cron: weekly odai cache #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: 'cron: weekly odai cache' | |
| run-name: 'cron: weekly odai cache' | |
| # Fleet-canonical on-device model cache. The fleet's keyless-AI lane (the | |
| # weekly-update decision leg, every odai-driven assist) needs the on-device | |
| # model present on a fresh runner: this proves the chrome-builtin backend | |
| # runs on a public ubuntu runner, fills the ~4 GB Gemini Nano component into | |
| # a cacheable profile, and verifies a FRESH runner restores it and prompts | |
| # offline. | |
| # | |
| # The weekly schedule is load-bearing twice over: Actions caches evict after | |
| # 7 idle days, and prune-actions-caches never evicts an entry accessed within | |
| # its fresh window — a weekly touch keeps the one 4 GB profile entry both | |
| # alive and prune-protected. | |
| # | |
| # Unlike the fleet seam (exit 69 = clean skip), this workflow FAILS LOUD on | |
| # an unavailable backend: its whole job is evidence. | |
| # | |
| # The setup-odai composite provisions the pinned CLI and verified Chrome. | |
| # Both jobs exercise the published consumer entry, independent of repo builds. | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '17 6 * * 1' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: on-device-model-cache | |
| cancel-in-progress: false | |
| env: | |
| ODAI_PROFILE_DIR: /home/runner/.cache/odai/chrome-builtin | |
| jobs: | |
| fill: | |
| cache-mode: write | |
| name: Prepare model cache | |
| runs-on: ${{ vars.ODAI_RUNNER || 'ubuntu-26.04' }} | |
| timeout-minutes: 50 | |
| outputs: | |
| component-version: ${{ steps.component.outputs.version }} | |
| cache-key: ${{ steps.component.outputs.cache-key }} | |
| steps: | |
| - name: Bootstrap checkout | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| SERVER_URL: ${{ github.server_url }} | |
| REPOSITORY: ${{ github.repository }} | |
| TRIGGER_REF: ${{ github.ref }} | |
| run: | | |
| set -euo pipefail | |
| git init -q | |
| git config --local advice.detachedHead false | |
| git remote remove origin 2>/dev/null || true | |
| git remote add origin "${SERVER_URL}/${REPOSITORY}" | |
| FETCH_ARGS=(--no-tags --prune --depth 1 origin "${TRIGGER_REF}") | |
| if [ -n "${GITHUB_TOKEN}" ]; then | |
| AUTH_B64="$(printf 'x-access-token:%s' "${GITHUB_TOKEN}" | base64 | tr -d '\n')" | |
| export GIT_CONFIG_COUNT=1 | |
| export GIT_CONFIG_KEY_0="http.${SERVER_URL}/.extraheader" | |
| export GIT_CONFIG_VALUE_0="AUTHORIZATION: basic ${AUTH_B64}" | |
| git fetch "${FETCH_ARGS[@]}" | |
| else | |
| git fetch "${FETCH_ARGS[@]}" | |
| fi | |
| git checkout -q --detach FETCH_HEAD | |
| # The model download wants ~22 GB free and Chrome REMOVES an installed | |
| # model when free disk drops under 10 GB — the stock runner image does | |
| # not leave that headroom, so the unused preinstalled toolchains go. | |
| - name: Reclaim runner disk | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| df -h / | tail -1 | |
| sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc \ | |
| /usr/local/.ghcup /opt/hostedtoolcache/CodeQL | |
| df -h / | tail -1 | |
| - name: 'Set up and install' | |
| uses: ./.github/actions/fleet/setup-and-install | |
| with: | |
| socket-api-token: ${{ secrets.SOCKET_API_TOKEN_FOR_CLI_AND_SFW }} | |
| # Reuse the Release App for a contents:read token scoped to wheelhouse. | |
| # Both credentials enable the private release fallback. Without the | |
| # key, hydration still pulls public GHCR anonymously. | |
| payload-token-client-id: ${{ secrets.SOCKET_RELEASE_CLIENT_ID || vars.SOCKET_RELEASE_CLIENT_ID }} | |
| payload-token-private-key: ${{ secrets.SOCKET_RELEASE_APP_PRIVATE_KEY }} | |
| - name: Verify model CPU capacity | |
| timeout-minutes: 1 | |
| shell: bash | |
| env: | |
| ODAI_CHROME_USER_DATA_DIR: /home/runner/.cache/odai/chrome-builtin | |
| run: | | |
| node scripts/fleet/ai/odai/diagnostics.mts --check-capacity --json | |
| # Cache the model components + activation state ONLY — the same set the | |
| # bridge's clone mode copies from a system profile. The rest of the | |
| # user-data-dir is Chrome litter (GPU/code caches, crashpad) that would | |
| # grow the entry on every weekly refill without bounding benefit. | |
| - name: Restore any prior model profile | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node scripts/fleet/cache/restore.mts \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceModel" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/optimization_guide_model_store" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceClassifierModel" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/Local State" \ | |
| --key "odai-nano-Linux-x64-fill-anchor" \ | |
| --restore-key "odai-nano-Linux-x64-" | |
| - name: Provision the pinned odai CLI | |
| uses: ./.github/actions/fleet/setup-odai | |
| with: | |
| allow-fill: 'false' | |
| restore-model-cache: 'false' | |
| require-ready: 'false' | |
| # First activation of a fresh profile needs network for one keyless | |
| # component-metadata exchange; with a restored profile the download is | |
| # skipped and this doubles as the warm-path timing receipt. | |
| - name: Download and activate model | |
| id: fill | |
| shell: bash | |
| env: | |
| ODAI_CHROME_ALLOW_DOWNLOAD: '1' | |
| ODAI_CHROME_USER_DATA_DIR: /home/runner/.cache/odai/chrome-builtin | |
| run: | | |
| set -euo pipefail | |
| printf '%s\n' \ | |
| 'The fleet cascade synchronizes template-owned files across member' \ | |
| 'repositories. Each wave reads the committed template state, writes' \ | |
| 'byte-identical copies into every member, and commits the result' \ | |
| 'with a receipt naming the template commit it mirrors. Drift between' \ | |
| 'a template file and a member copy is a defect the next wave heals.' \ | |
| > /tmp/on-device-model-cache-input.txt | |
| START="$(date +%s)" | |
| odai summarize --backend chrome-builtin \ | |
| --input /tmp/on-device-model-cache-input.txt --timeout 240000 \ | |
| > /tmp/on-device-model-cache-fill.json | |
| ELAPSED="$(( $(date +%s) - START ))" | |
| node -e ' | |
| const fs = require("node:fs") | |
| const r = JSON.parse(fs.readFileSync("/tmp/on-device-model-cache-fill.json", "utf8")) | |
| if (typeof r.summary !== "string" || r.summary.length === 0) { | |
| console.error("on-device-model-cache: summarize reply carries no summary string.") | |
| process.exit(1) | |
| } | |
| console.log("summary:", r.summary) | |
| ' | |
| echo "fill prompt wall time: ${ELAPSED}s" | |
| odai backends || true | |
| du -sh /home/runner/.cache/odai/chrome-builtin | |
| - name: Diagnose model readiness | |
| if: ${{ failure() && steps.fill.outcome == 'failure' }} | |
| timeout-minutes: 1 | |
| shell: bash | |
| env: | |
| ODAI_CHROME_USER_DATA_DIR: /home/runner/.cache/odai/chrome-builtin | |
| run: | | |
| node scripts/fleet/ai/odai/diagnostics.mts --json | |
| - name: Read the component version | |
| id: component | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DIR=/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceModel | |
| if [ ! -d "$DIR" ]; then | |
| echo "::error::on-device-model-cache: no model component after the fill." >&2 | |
| echo "::error::Where: $DIR on the fill runner." >&2 | |
| echo "::error::Saw vs wanted: directory absent; wanted one <component-version> subdir." >&2 | |
| echo "::error::Fix: read the fill-step log — availability() reasons are printed by the CLI." >&2 | |
| exit 1 | |
| fi | |
| VERSIONS=("$DIR"/*) | |
| test -e "${VERSIONS[0]}" | |
| VERSION="${VERSIONS[0]##*/}" | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "cache-key=odai-nano-Linux-x64-${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "component version: ${VERSION}" | |
| - name: Save the profile cache | |
| shell: bash | |
| env: | |
| CACHE_KEY: ${{ steps.component.outputs.cache-key }} | |
| run: | | |
| set -euo pipefail | |
| node scripts/fleet/cache/save.mts \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceModel" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/optimization_guide_model_store" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceClassifierModel" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/Local State" \ | |
| --key "$CACHE_KEY" | |
| verify: | |
| cache-mode: read | |
| name: Verify model cache | |
| needs: fill | |
| runs-on: ${{ vars.ODAI_RUNNER || 'ubuntu-26.04' }} | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Bootstrap checkout | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| SERVER_URL: ${{ github.server_url }} | |
| REPOSITORY: ${{ github.repository }} | |
| TRIGGER_REF: ${{ github.ref }} | |
| run: | | |
| set -euo pipefail | |
| git init -q | |
| git config --local advice.detachedHead false | |
| git remote remove origin 2>/dev/null || true | |
| git remote add origin "${SERVER_URL}/${REPOSITORY}" | |
| FETCH_ARGS=(--no-tags --prune --depth 1 origin "${TRIGGER_REF}") | |
| if [ -n "${GITHUB_TOKEN}" ]; then | |
| AUTH_B64="$(printf 'x-access-token:%s' "${GITHUB_TOKEN}" | base64 | tr -d '\n')" | |
| export GIT_CONFIG_COUNT=1 | |
| export GIT_CONFIG_KEY_0="http.${SERVER_URL}/.extraheader" | |
| export GIT_CONFIG_VALUE_0="AUTHORIZATION: basic ${AUTH_B64}" | |
| git fetch "${FETCH_ARGS[@]}" | |
| else | |
| git fetch "${FETCH_ARGS[@]}" | |
| fi | |
| git checkout -q --detach FETCH_HEAD | |
| - name: 'Set up and install' | |
| uses: ./.github/actions/fleet/setup-and-install | |
| with: | |
| socket-api-token: ${{ secrets.SOCKET_API_TOKEN_FOR_CLI_AND_SFW }} | |
| # Reuse the Release App for a contents:read token scoped to wheelhouse. | |
| # Both credentials enable the private release fallback. Without the | |
| # key, hydration still pulls public GHCR anonymously. | |
| payload-token-client-id: ${{ secrets.SOCKET_RELEASE_CLIENT_ID || vars.SOCKET_RELEASE_CLIENT_ID }} | |
| payload-token-private-key: ${{ secrets.SOCKET_RELEASE_APP_PRIVATE_KEY }} | |
| - name: Verify model CPU capacity | |
| timeout-minutes: 1 | |
| shell: bash | |
| env: | |
| ODAI_CHROME_USER_DATA_DIR: /home/runner/.cache/odai/chrome-builtin | |
| run: | | |
| node scripts/fleet/ai/odai/diagnostics.mts --check-capacity --json | |
| - name: Restore the filled profile (miss = failure) | |
| shell: bash | |
| env: | |
| CACHE_KEY: ${{ needs.fill.outputs.cache-key }} | |
| run: | | |
| set -euo pipefail | |
| node scripts/fleet/cache/restore.mts \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceModel" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/optimization_guide_model_store" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/OptGuideOnDeviceClassifierModel" \ | |
| --path "/home/runner/.cache/odai/chrome-builtin/Local State" \ | |
| --key "$CACHE_KEY" \ | |
| --fail-on-miss | |
| - name: Provision the pinned odai CLI | |
| uses: ./.github/actions/fleet/setup-odai | |
| with: | |
| allow-fill: 'false' | |
| restore-model-cache: 'false' | |
| require-ready: 'true' | |
| - name: Verify model offline | |
| shell: bash | |
| env: | |
| ODAI_CHROME_ALLOW_DOWNLOAD: '0' | |
| ODAI_CHROME_USER_DATA_DIR: /home/runner/.cache/odai/chrome-builtin | |
| run: | | |
| set -euo pipefail | |
| printf '%s\n' \ | |
| 'A worktree keeps branch work out of the primary checkout. Each' \ | |
| 'worktree shares the same object store but holds its own files,' \ | |
| 'so an agent can build and test a feature branch in isolation' \ | |
| 'while the default branch stays clean for other sessions.' \ | |
| > /tmp/on-device-model-cache-verify.txt | |
| START="$(date +%s)" | |
| odai summarize --backend chrome-builtin \ | |
| --input /tmp/on-device-model-cache-verify.txt --timeout 240000 \ | |
| > /tmp/on-device-model-cache-verify.json | |
| ELAPSED="$(( $(date +%s) - START ))" | |
| node -e ' | |
| const fs = require("node:fs") | |
| const r = JSON.parse(fs.readFileSync("/tmp/on-device-model-cache-verify.json", "utf8")) | |
| if (typeof r.summary !== "string" || r.summary.length === 0) { | |
| console.error("on-device-model-cache: offline summarize reply carries no summary string.") | |
| process.exit(1) | |
| } | |
| console.log("summary:", r.summary) | |
| ' | |
| echo "restored-profile prompt wall time: ${ELAPSED}s" |