From 53acbfb787acbe08360ed0a4ff324a316d812fda Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 4 Oct 2026 11:00:54 +0800 Subject: [PATCH 1/2] Add combined E2B Runtime template build workflow --- .github/workflows/e2b-template.yml | 152 +++++++++++++++++++++++++++++ deploy/e2b/README.md | 24 +++++ deploy/e2b/build-runtime.sh | 40 ++++++++ 3 files changed, 216 insertions(+) create mode 100644 .github/workflows/e2b-template.yml create mode 100644 deploy/e2b/README.md create mode 100644 deploy/e2b/build-runtime.sh diff --git a/.github/workflows/e2b-template.yml b/.github/workflows/e2b-template.yml new file mode 100644 index 000000000..5f182b283 --- /dev/null +++ b/.github/workflows/e2b-template.yml @@ -0,0 +1,152 @@ +name: e2b-template-build + +on: + workflow_dispatch: + inputs: + source_branch: + description: Source branch for the combined Runtime + type: choice + options: [main, beta] + default: main + required: true + ref: + description: Full commit SHA in that branch; empty selects its current tip + type: string + required: false + +permissions: + contents: read + +concurrency: + group: e2b-template-build + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-22.04 + environment: e2b-build + timeout-minutes: 120 + steps: + - name: Check build settings + env: + WORKFLOW_REF: ${{ github.ref }} + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + E2B_API_URL: ${{ vars.E2B_API_URL }} + run: | + set -euo pipefail + [ "$WORKFLOW_REF" = refs/heads/main ] || { echo '::error::Run this workflow from main.'; exit 1; } + [ -n "$E2B_API_KEY" ] || { echo '::error::Set the E2B_API_KEY secret in e2b-build.'; exit 1; } + python3 - <<'PY' + import os, urllib.parse + value = os.environ['E2B_API_URL'] + try: + url = urllib.parse.urlsplit(value) + valid = (url.scheme == 'https' and bool(url.hostname) + and url.username is None and url.password is None + and not url.path and not url.query and not url.fragment + and not any(c in value for c in '\\ \t\r\n?#%')) + if url.port is not None: + valid = valid and 1 <= url.port <= 65535 + except ValueError: + valid = False + if not valid: + raise SystemExit('Set E2B_API_URL to an HTTPS API origin without path or credentials.') + PY + - uses: actions/checkout@v7 + with: + path: build-tools + persist-credentials: false + fetch-depth: 0 + - name: Pin source revision + id: source + working-directory: build-tools + env: + SOURCE_BRANCH: ${{ inputs.source_branch }} + REQUESTED_REF: ${{ inputs.ref }} + run: | + set -euo pipefail + case "$SOURCE_BRANCH" in main|beta) ;; *) exit 1 ;; esac + if [ -n "$REQUESTED_REF" ] && [[ ! "$REQUESTED_REF" =~ ^[0-9a-f]{40}$ ]]; then + echo '::error::ref must be a full lowercase commit SHA.' + exit 1 + fi + git fetch --no-tags origin "refs/heads/$SOURCE_BRANCH:refs/remotes/origin/$SOURCE_BRANCH" + revision="$(git rev-parse "${REQUESTED_REF:-origin/$SOURCE_BRANCH}^{commit}")" + git merge-base --is-ancestor "$revision" "origin/$SOURCE_BRANCH" || { + echo '::error::ref must be in the selected source branch.'; exit 1; + } + echo "revision=$revision" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v7 + with: + path: source + ref: ${{ steps.source.outputs.revision }} + persist-credentials: false + - uses: actions/setup-go@v7 + with: + go-version-file: source/go.mod + cache: false + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + - uses: ./build-tools/.github/actions/node + with: + lockfiles: source/packages/claude-sdk-adapter/pnpm-lock.yaml + - name: Enable Corepack for the pinned MiniMax source build + run: corepack enable + - name: Select shared Go caches + run: | + echo "GOCACHE=$HOME/.oac/cache/go-build" >> "$GITHUB_ENV" + echo "GOMODCACHE=$HOME/.oac/cache/go-mod" >> "$GITHUB_ENV" + - uses: actions/cache@v6 + with: + path: | + ~/.oac/cache/go-build + ~/.oac/cache/go-mod + key: e2b-runtime-go-${{ runner.os }}-${{ hashFiles('source/**/go.mod', 'source/**/go.sum') }} + - name: Install the selected revision's pinned E2B SDK + run: | + python3 -m venv "$RUNNER_TEMP/e2b-sdk" + "$RUNNER_TEMP/e2b-sdk/bin/pip" install -r source/services/core/deploy/e2b/requirements.txt + - name: Build the combined Runtime image + run: | + bash build-tools/deploy/e2b/build-runtime.sh "$GITHUB_WORKSPACE/source" "$HOME/.oac/build/e2b-runtime" + - name: Build one immutable E2B template + env: + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + E2B_API_URL: ${{ vars.E2B_API_URL }} + run: | + set -euo pipefail + umask 077 + key="$(mktemp "$RUNNER_TEMP/e2b-key.XXXXXX")" + trap 'rm -f "$key"' EXIT + printf '%s' "$E2B_API_KEY" > "$key" + unset E2B_API_KEY + mkdir -p "$RUNNER_TEMP/e2b-result" + "$RUNNER_TEMP/e2b-sdk/bin/python" source/services/core/deploy/e2b/build-template.py \ + --image "$(cat "$HOME/.oac/build/e2b-runtime/runtime-image.id")" \ + --name "sandbase-oac-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" \ + --api-key-file "$key" --output "$RUNNER_TEMP/e2b-result/template.json" + - name: Record the build identity + env: + SOURCE_COMMIT: ${{ steps.source.outputs.revision }} + SOURCE_BRANCH: ${{ inputs.source_branch }} + E2B_API_URL: ${{ vars.E2B_API_URL }} + run: | + python3 - <<'PY' + import json, os, pathlib + report = pathlib.Path(os.environ['RUNNER_TEMP']) / 'e2b-result/template.json' + value = json.loads(report.read_text()) + value.update(source_commit=os.environ['SOURCE_COMMIT'], source_branch=os.environ['SOURCE_BRANCH'], + api_url=os.environ['E2B_API_URL'], harnesses=['codex', 'claude_sdk', 'mcode'], + qualification='image identity and native package checks; no live Session/Turn') + report.write_text(json.dumps(value, indent=2) + '\n') + with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as out: + out.write(f"Source: `{value['source_commit']}`\n\nTemplate: `{value['template']}`\n\nImage: `{value['image']}`\n\n") + out.write('One combined Runtime template built. No Core configuration or production deployment was changed.\n') + PY + - uses: actions/upload-artifact@v6 + with: + name: e2b-template-${{ steps.source.outputs.revision }}-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/e2b-result/template.json + if-no-files-found: error + retention-days: 90 diff --git a/deploy/e2b/README.md b/deploy/e2b/README.md new file mode 100644 index 000000000..0da9f2e1f --- /dev/null +++ b/deploy/e2b/README.md @@ -0,0 +1,24 @@ +# Combined E2B template build + +Run **Actions → e2b-template-build → Run workflow** from `main`. Select `main` or `beta` as `source_branch`; optionally enter a full commit SHA belonging to that branch. An empty `ref` pins the branch tip once at the start of the run. + +This fork-owned workflow builds one Linux amd64 Runtime image containing `oac-daemon`, Codex, Claude SDK and MiniMax Code, then packages it as one E2B template using the selected revision's maintained builders and pinned dependencies. The three intermediate images are local build stages, not three templates. The build uses the maintained `deploy/distribution/Runtime.Dockerfile` and does not build Core, Web or a full offline distribution. + +## GitHub configuration + +Create the `e2b-build` GitHub Environment and restrict its deployment branches to `main`. Configure: + +| Kind | Name | Value | +| --- | --- | --- | +| Secret | `E2B_API_KEY` | The key for the E2B-compatible account that owns the template | +| Variable | `E2B_API_URL` | The exact HTTPS API origin, without a path or trailing slash; use `https://api.e2b.app` for official E2B or your compatible service's endpoint | + +Both are required. For the SandBase endpoint, set `E2B_API_URL` to `https://sandbox.sandbase.ai` (HTTPS, without the trailing slash). The workflow supplies the endpoint through the pinned SDK's `E2B_API_URL` setting. Template creation/upload/build APIs must be implemented by the endpoint; Sandbox Create compatibility alone does not establish template-build support. Returned upload destinations must be reachable from GitHub's hosted runner. The builder currently requests 2 vCPUs and 2048 MiB, as defined by the upstream [template builder](../../services/core/deploy/e2b/README.md#build-a-template). + +The E2B key is available only to the settings check and template-build steps. It is temporarily written to a private file, removed on exit, excluded from Runtime images and build reports, and never sent to a model provider. No model credentials are needed to build the template. A run creates a cloud template build and may incur the provider's build charges; do not trigger a run merely to validate workflow syntax. + +## Output and qualification + +The run summary and the `e2b-template-*` artifact contain `template.json`: the immutable `templateID:build_UUID`, source commit and branch, image ID, packaged Runtime checksum, base image, endpoint and advertised Harnesses. Template names include the run ID and attempt so another run does not replace this build's name. There is no automatic retry of an uncertain cloud build; inspect the provider before starting another run after a failure. + +The image checks verify committed daemon/adapter identity and native package loading/version checks. Template build completion establishes packaging readiness, not Core enrollment, real model execution or pause/resume qualification. The workflow changes no active Core selection, Kubernetes resource or production Session. [Sandbox deployment](../../contracts/agents-api/sandbox-deployment.md) owns later template selection and generation behavior. diff --git a/deploy/e2b/build-runtime.sh b/deploy/e2b/build-runtime.sh new file mode 100644 index 000000000..3a268cb18 --- /dev/null +++ b/deploy/e2b/build-runtime.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Reuse the selected revision's native builders and combined Runtime Dockerfile. +source_root="${1:?Usage: build-runtime.sh SOURCE_ROOT OUTPUT_ROOT}" +output_root="${2:?Usage: build-runtime.sh SOURCE_ROOT OUTPUT_ROOT}" +[[ "$source_root" == /* && "$output_root" == /* ]] +[[ "$(uname -s):$(uname -m)" == Linux:x86_64 ]] +cd "$source_root" +mkdir -p "$output_root" +bash scripts/prepare-release-runtimes.sh +inputs="$HOME/.oac/build/release-inputs/inputs.json" +AGENTS_RUNTIME_CODEX_PACKAGE="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["codex"])' "$inputs")" +MCODE_HARNESS_BUILD_DIR="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["mcode"])' "$inputs")" +CLAUDE_SDK_BUILD_DIR="$output_root/claude-sdk" +export AGENTS_RUNTIME_CODEX_PACKAGE MCODE_HARNESS_BUILD_DIR CLAUDE_SDK_BUILD_DIR +bash scripts/build-claude-sdk-runtime.sh +revision="$(git rev-parse HEAD)" +for harness in codex claude mcode; do + builder="scripts/build-$harness-runtime.sh" + if [[ "$harness" == codex ]]; then builder=scripts/build-agents-runtime.sh; fi + AGENTS_RUNTIME_BUILD_DIR="$output_root/$harness" bash "$builder" + docker build --platform linux/amd64 --tag "oac-e2b-$harness:$revision" "$output_root/$harness" + image="$(docker image inspect --format '{{.Id}}' "oac-e2b-$harness:$revision")" + python3 scripts/core-distribution-manifest.py verify-runtime \ + "$image" "$output_root/$harness/oac-daemon" "$source_root" +done +# The maintained multi-stage Dockerfile advertises and checks all three Harnesses. +mkdir -p "$output_root/combined" +cp deploy/distribution/Runtime.Dockerfile "$output_root/combined/Dockerfile" +docker build --platform linux/amd64 \ + --build-arg "CODEX_IMAGE=oac-e2b-codex:$revision" \ + --build-arg "CLAUDE_IMAGE=oac-e2b-claude:$revision" \ + --build-arg "MCODE_IMAGE=oac-e2b-mcode:$revision" \ + --label "org.opencontainers.image.revision=$revision" \ + --tag "oac-e2b-runtime:$revision" "$output_root/combined" +image="$(docker image inspect --format '{{.Id}}' "oac-e2b-runtime:$revision")" +python3 scripts/core-distribution-manifest.py verify-runtime \ + "$image" "$output_root/mcode/oac-daemon" "$source_root" +printf '%s\n' "$image" > "$output_root/runtime-image.id" From ba7cea8e1b1d3e57fe1312be9349da8249c2e41a Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 4 Oct 2026 11:07:43 +0800 Subject: [PATCH 2/2] Keep E2B build orchestration inside the workflow --- .github/workflows/e2b-template.yml | 37 ++++++++++++++++++++++++++- deploy/e2b/build-runtime.sh | 40 ------------------------------ 2 files changed, 36 insertions(+), 41 deletions(-) delete mode 100644 deploy/e2b/build-runtime.sh diff --git a/.github/workflows/e2b-template.yml b/.github/workflows/e2b-template.yml index 5f182b283..44fce7615 100644 --- a/.github/workflows/e2b-template.yml +++ b/.github/workflows/e2b-template.yml @@ -108,8 +108,43 @@ jobs: python3 -m venv "$RUNNER_TEMP/e2b-sdk" "$RUNNER_TEMP/e2b-sdk/bin/pip" install -r source/services/core/deploy/e2b/requirements.txt - name: Build the combined Runtime image + working-directory: source run: | - bash build-tools/deploy/e2b/build-runtime.sh "$GITHUB_WORKSPACE/source" "$HOME/.oac/build/e2b-runtime" + set -euo pipefail + source_root="$GITHUB_WORKSPACE/source" + output_root="$HOME/.oac/build/e2b-runtime" + cd "$source_root" + mkdir -p "$output_root" + bash scripts/prepare-release-runtimes.sh + inputs="$HOME/.oac/build/release-inputs/inputs.json" + AGENTS_RUNTIME_CODEX_PACKAGE="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["codex"])' "$inputs")" + MCODE_HARNESS_BUILD_DIR="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["mcode"])' "$inputs")" + CLAUDE_SDK_BUILD_DIR="$output_root/claude-sdk" + export AGENTS_RUNTIME_CODEX_PACKAGE MCODE_HARNESS_BUILD_DIR CLAUDE_SDK_BUILD_DIR + bash scripts/build-claude-sdk-runtime.sh + revision="$(git rev-parse HEAD)" + for harness in codex claude mcode; do + builder="scripts/build-$harness-runtime.sh" + if [[ "$harness" == codex ]]; then builder=scripts/build-agents-runtime.sh; fi + AGENTS_RUNTIME_BUILD_DIR="$output_root/$harness" bash "$builder" + docker build --platform linux/amd64 --tag "oac-e2b-$harness:$revision" "$output_root/$harness" + image="$(docker image inspect --format '{{.Id}}' "oac-e2b-$harness:$revision")" + python3 scripts/core-distribution-manifest.py verify-runtime \ + "$image" "$output_root/$harness/oac-daemon" "$source_root" + done + # The maintained multi-stage Dockerfile advertises and checks all three Harnesses. + mkdir -p "$output_root/combined" + cp deploy/distribution/Runtime.Dockerfile "$output_root/combined/Dockerfile" + docker build --platform linux/amd64 \ + --build-arg "CODEX_IMAGE=oac-e2b-codex:$revision" \ + --build-arg "CLAUDE_IMAGE=oac-e2b-claude:$revision" \ + --build-arg "MCODE_IMAGE=oac-e2b-mcode:$revision" \ + --label "org.opencontainers.image.revision=$revision" \ + --tag "oac-e2b-runtime:$revision" "$output_root/combined" + image="$(docker image inspect --format '{{.Id}}' "oac-e2b-runtime:$revision")" + python3 scripts/core-distribution-manifest.py verify-runtime \ + "$image" "$output_root/mcode/oac-daemon" "$source_root" + printf '%s\n' "$image" > "$output_root/runtime-image.id" - name: Build one immutable E2B template env: E2B_API_KEY: ${{ secrets.E2B_API_KEY }} diff --git a/deploy/e2b/build-runtime.sh b/deploy/e2b/build-runtime.sh deleted file mode 100644 index 3a268cb18..000000000 --- a/deploy/e2b/build-runtime.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Reuse the selected revision's native builders and combined Runtime Dockerfile. -source_root="${1:?Usage: build-runtime.sh SOURCE_ROOT OUTPUT_ROOT}" -output_root="${2:?Usage: build-runtime.sh SOURCE_ROOT OUTPUT_ROOT}" -[[ "$source_root" == /* && "$output_root" == /* ]] -[[ "$(uname -s):$(uname -m)" == Linux:x86_64 ]] -cd "$source_root" -mkdir -p "$output_root" -bash scripts/prepare-release-runtimes.sh -inputs="$HOME/.oac/build/release-inputs/inputs.json" -AGENTS_RUNTIME_CODEX_PACKAGE="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["codex"])' "$inputs")" -MCODE_HARNESS_BUILD_DIR="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["mcode"])' "$inputs")" -CLAUDE_SDK_BUILD_DIR="$output_root/claude-sdk" -export AGENTS_RUNTIME_CODEX_PACKAGE MCODE_HARNESS_BUILD_DIR CLAUDE_SDK_BUILD_DIR -bash scripts/build-claude-sdk-runtime.sh -revision="$(git rev-parse HEAD)" -for harness in codex claude mcode; do - builder="scripts/build-$harness-runtime.sh" - if [[ "$harness" == codex ]]; then builder=scripts/build-agents-runtime.sh; fi - AGENTS_RUNTIME_BUILD_DIR="$output_root/$harness" bash "$builder" - docker build --platform linux/amd64 --tag "oac-e2b-$harness:$revision" "$output_root/$harness" - image="$(docker image inspect --format '{{.Id}}' "oac-e2b-$harness:$revision")" - python3 scripts/core-distribution-manifest.py verify-runtime \ - "$image" "$output_root/$harness/oac-daemon" "$source_root" -done -# The maintained multi-stage Dockerfile advertises and checks all three Harnesses. -mkdir -p "$output_root/combined" -cp deploy/distribution/Runtime.Dockerfile "$output_root/combined/Dockerfile" -docker build --platform linux/amd64 \ - --build-arg "CODEX_IMAGE=oac-e2b-codex:$revision" \ - --build-arg "CLAUDE_IMAGE=oac-e2b-claude:$revision" \ - --build-arg "MCODE_IMAGE=oac-e2b-mcode:$revision" \ - --label "org.opencontainers.image.revision=$revision" \ - --tag "oac-e2b-runtime:$revision" "$output_root/combined" -image="$(docker image inspect --format '{{.Id}}' "oac-e2b-runtime:$revision")" -python3 scripts/core-distribution-manifest.py verify-runtime \ - "$image" "$output_root/mcode/oac-daemon" "$source_root" -printf '%s\n' "$image" > "$output_root/runtime-image.id"