Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/build-docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
github.ref_name == 'build-docker-image')
name: Start areal-docker-builder instance
runs-on: ubuntu-latest
outputs:
was_running: ${{ steps.start-instance.outputs.was_running }}
env:
INSTANCE_NAME: areal-docker-builder
INSTANCE_ZONE: us-central1-f
Expand All @@ -45,6 +47,7 @@ jobs:
uses: google-github-actions/setup-gcloud@v2

- name: Start builder instance if stopped
id: start-instance
run: |
set -euo pipefail

Expand All @@ -61,12 +64,14 @@ jobs:

if [ "$status" = "RUNNING" ]; then
echo "Instance $INSTANCE_NAME is already running."
echo "was_running=true" >> $GITHUB_OUTPUT
elif [ "$status" = "TERMINATED" ] || [ "$status" = "SUSPENDED" ]; then
echo "Instance $INSTANCE_NAME is $status. Starting it..."
gcloud compute instances start "$INSTANCE_NAME" \
--project "$GCP_PROJECT_ID" \
--zone "$INSTANCE_ZONE"
echo "Instance started successfully."
echo "was_running=false" >> $GITHUB_OUTPUT
else
echo "Instance $INSTANCE_NAME has unexpected status: $status" >&2
exit 1
Expand Down Expand Up @@ -163,7 +168,7 @@ jobs:
needs:
- build-and-push-image
- start-builder
if: always()
if: always() && needs.start-builder.outputs.was_running != 'true'
runs-on: ubuntu-latest
env:
INSTANCE_NAME: areal-docker-builder
Expand Down
155 changes: 155 additions & 0 deletions .github/workflows/runner-heartbeat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Runner Heartbeat

on:
schedule:
- cron: '0 0 */7 * *' # Every 7 days at midnight UTC
workflow_dispatch:

concurrency:
group: runner-heartbeat
cancel-in-progress: true

permissions:
contents: read

env:
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}

jobs:
start-builder:
name: Start areal-docker-builder instance
runs-on: ubuntu-latest
env:
INSTANCE_NAME: areal-docker-builder
INSTANCE_ZONE: us-central1-f
steps:
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}

- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2

- name: Start builder instance if stopped
run: |
set -euo pipefail

status=$(gcloud compute instances describe "$INSTANCE_NAME" \
--project "$GCP_PROJECT_ID" \
--zone "$INSTANCE_ZONE" \
--format="get(status)" || echo "NOT_FOUND")

if [ "$status" = "NOT_FOUND" ]; then
echo "Error: Instance $INSTANCE_NAME not found in zone $INSTANCE_ZONE" >&2
exit 1
fi

if [ "$status" = "RUNNING" ]; then
echo "Instance $INSTANCE_NAME is already running."
elif [ "$status" = "TERMINATED" ] || [ "$status" = "SUSPENDED" ]; then
echo "Instance $INSTANCE_NAME is $status. Starting it..."
gcloud compute instances start "$INSTANCE_NAME" \
--project "$GCP_PROJECT_ID" \
--zone "$INSTANCE_ZONE"
echo "Instance started successfully."
else
echo "Instance $INSTANCE_NAME has unexpected status: $status" >&2
exit 1
fi

- name: Wait for builder runner to be online
uses: actions/github-script@v7
env:
INSTANCE_NAME: areal-docker-builder
GH_PAT: ${{ secrets.GH_PAT }}
with:
github-token: ${{ secrets.GH_PAT }}
script: |
const instanceName = process.env.INSTANCE_NAME;
const maxAttempts = 120;
const delayMs = 10000;

const pat = process.env.GH_PAT;
if (!pat) {
core.setFailed('GH_PAT secret is not configured.');
return;
}

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const response = await github.rest.actions.listSelfHostedRunnersForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
request: {
headers: {
authorization: `token ${pat}`,
},
},
});

const found = response.data.runners.find((runner) => runner.name === instanceName);
if (found && found.status === 'online') {
core.info(`Builder runner ${instanceName} is online.`);
return;
}

core.info(`Builder runner ${instanceName} not ready yet (attempt ${attempt}/${maxAttempts}).`);
await wait(delayMs);
}

throw new Error(`Timed out waiting for builder runner ${instanceName} to come online.`);

heartbeat:
name: Runner heartbeat
needs: start-builder
runs-on: [self-hosted, areal-docker-builder]
steps:
- name: Heartbeat
run: |
echo "Runner heartbeat at $(date -u)"
echo "Runner: $(hostname)"
echo "Uptime: $(uptime)"

stop-builder:
name: Stop areal-docker-builder instance
needs:
- heartbeat
- start-builder
if: always()
runs-on: ubuntu-latest
env:
INSTANCE_NAME: areal-docker-builder
INSTANCE_ZONE: us-central1-f
steps:
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}

- name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2

- name: Stop builder instance
run: |
status=$(gcloud compute instances describe "$INSTANCE_NAME" \
--project "$GCP_PROJECT_ID" \
--zone "$INSTANCE_ZONE" \
--format="get(status)" 2>/dev/null || echo "NOT_FOUND")

if [ "$status" = "NOT_FOUND" ]; then
echo "Warning: Instance $INSTANCE_NAME not found in zone $INSTANCE_ZONE"
exit 0
fi

if [ "$status" = "RUNNING" ]; then
echo "Stopping instance $INSTANCE_NAME..."
gcloud compute instances stop "$INSTANCE_NAME" \
--project "$GCP_PROJECT_ID" \
--zone "$INSTANCE_ZONE"
echo "Instance stopped successfully."
else
echo "Instance $INSTANCE_NAME is already in status: $status"
fi
82 changes: 56 additions & 26 deletions .github/workflows/tag-release-image.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
name: Tag Release Docker Image
name: Build Release Docker Image

on:
release:
types: [created]
workflow_dispatch:
inputs:
tag:
description: 'Release tag to build (e.g., v0.5.1)'
required: true
type: string

concurrency:
group: tag-release-image-${{ github.event.release.tag_name }}
group: tag-release-image-${{ github.event.release.tag_name || github.event.inputs.tag }}
cancel-in-progress: true

permissions:
Expand All @@ -15,7 +21,6 @@ permissions:
env:
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
IMAGE_NAME: ghcr.io/inclusionai/areal-runtime
SOURCE_TAG: dev

jobs:
start-builder:
Expand Down Expand Up @@ -105,47 +110,72 @@ jobs:

throw new Error(`Timed out waiting for builder runner ${instanceName} to come online.`);

tag-and-push-image:
build-and-push-release:
needs:
- start-builder
name: Tag and push release Docker image
name: Build and push release Docker image
runs-on: [self-hosted, areal-docker-builder]
timeout-minutes: 30
timeout-minutes: 180
outputs:
package_version: ${{ steps.get-version.outputs.version }}
release_tag: ${{ steps.get-version.outputs.release_tag }}
steps:
- name: Checkout code at release tag
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name || github.event.inputs.tag }}

- name: Get package version
id: get-version
run: |
# Extract version from areal/version.py
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' areal/version.py)
RELEASE_TAG="${{ github.event.release.tag_name || github.event.inputs.tag }}"

echo "Package version: $VERSION"
echo "Release tag: $RELEASE_TAG"

echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: inclusionai
password: ${{ secrets.GHCR_TOKEN }}

- name: Pull dev image
run: |
echo "Pulling source image: ${{ env.IMAGE_NAME }}:${{ env.SOURCE_TAG }}"
docker pull ${{ env.IMAGE_NAME }}:${{ env.SOURCE_TAG }}

- name: Tag image with release version
run: |
echo "Tagging image with release version: ${{ github.event.release.tag_name }}"
docker tag ${{ env.IMAGE_NAME }}:${{ env.SOURCE_TAG }} ${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}

- name: Push release image
run: |
echo "Pushing image: ${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}"
docker push ${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ steps.get-version.outputs.release_tag }}
${{ env.IMAGE_NAME }}:${{ steps.get-version.outputs.version }}
${{ env.IMAGE_NAME }}:latest

- name: Image details
run: |
echo "✅ Docker image tagged and pushed successfully!"
echo "Image: ${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}"
echo "Source: ${{ env.IMAGE_NAME }}:${{ env.SOURCE_TAG }}"
echo "Release: ${{ github.event.release.name }}"
echo "Tag: ${{ github.event.release.tag_name }}"
echo "Docker image built and pushed successfully!"
echo "Image: ${{ env.IMAGE_NAME }}"
echo "Tags:"
echo " - ${{ steps.get-version.outputs.release_tag }}"
echo " - ${{ steps.get-version.outputs.version }}"
echo " - latest"
echo "Release: ${{ github.event.release.name || github.event.inputs.tag }}"
echo "Commit: ${{ github.sha }}"

stop-builder:
name: Stop areal-docker-builder instance
needs:
- tag-and-push-image
- build-and-push-release
- start-builder
if: always()
runs-on: ubuntu-latest
Expand Down
24 changes: 17 additions & 7 deletions .github/workflows/test-areal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,40 +237,50 @@ jobs:
contents: read
runs-on: [self-hosted, gcp-a2-highgpu-2g]
timeout-minutes: 120
env:
# Activate the venv created in the Docker image
VIRTUAL_ENV: /AReaL/.venv
steps:
- uses: actions/checkout@v4

- name: Validate Docker installation
run: |
export PATH="/AReaL/.venv/bin:$PATH"
python areal/tools/validate_docker_installation.py

- name: Install python packages for testing (legacy support)
run: |
pip install pytest-asyncio openai-agents

- name: Run unit tests
env:
CI: true
HF_TOKEN: ${{ secrets.HF_TOKEN }}
PYTHONPATH: ${{ github.workspace }}
TOKENIZERS_PARALLELISM: false
AREAL_IS_IN_CI: 1
VIRTUAL_ENV: /AReaL/.venv
run: |
TOKENIZERS_PARALLELISM=false AREAL_IS_IN_CI=1 pytest -m "not slow or ci" --durations=20 -s -vv areal/tests/test_*.py areal/tests/experimental/
export PATH="/AReaL/.venv/bin:$PATH"
pytest -m "not slow or ci" --durations=20 -s -vv areal/tests/test_*.py areal/tests/experimental/

- name: Run SFT integration tests
env:
CI: true
HF_TOKEN: ${{ secrets.HF_TOKEN }}
PYTHONPATH: ${{ github.workspace }}
TOKENIZERS_PARALLELISM: false
VIRTUAL_ENV: /AReaL/.venv
run: |
TOKENIZERS_PARALLELISM=false pytest -s -vv areal/tests/sft/
export PATH="/AReaL/.venv/bin:$PATH"
pytest -s -vv areal/tests/sft/

- name: Run GRPO integration tests
env:
CI: true
HF_TOKEN: ${{ secrets.HF_TOKEN }}
PYTHONPATH: ${{ github.workspace }}
TOKENIZERS_PARALLELISM: false
VIRTUAL_ENV: /AReaL/.venv
run: |
TOKENIZERS_PARALLELISM=false pytest -s -vv areal/tests/grpo/
export PATH="/AReaL/.venv/bin:$PATH"
pytest -s -vv areal/tests/grpo/

cleanup:
name: Tear down GCP runner
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
.data/
.idea/

# claude
.claude/
.agent/

# Ruff
.ruff_cache/
Expand Down
Loading