426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
424 lines
16 KiB
YAML
424 lines
16 KiB
YAML
name: Feed RL Training
|
|
|
|
# Trigger options:
|
|
# 1. On schedule (daily training at 2 AM UTC)
|
|
# 2. Via repository_dispatch (from debug endpoint)
|
|
# 3. Manual workflow_dispatch (testing from GitHub UI)
|
|
on:
|
|
schedule:
|
|
# Daily at 2 AM UTC
|
|
- cron: '0 2 * * *'
|
|
|
|
repository_dispatch:
|
|
types: [trigger-training]
|
|
|
|
workflow_dispatch:
|
|
inputs:
|
|
batch_id:
|
|
description: 'Training batch ID (optional, auto-generated if not provided)'
|
|
required: false
|
|
type: string
|
|
window_id:
|
|
description: 'Window ID to train (optional, auto-detected if not provided)'
|
|
required: false
|
|
type: string
|
|
force:
|
|
description: 'Force training even if not ready (for testing)'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
base_model:
|
|
description: 'Base model to use (default: google/gemma-4-E4B-it)'
|
|
required: false
|
|
type: string
|
|
|
|
env:
|
|
PYTHON_VERSION: '3.11'
|
|
FEED_DEFAULT_BASE_MODEL: 'google/gemma-4-E4B-it'
|
|
|
|
# Prevent concurrent training runs
|
|
concurrency:
|
|
group: training-pipeline
|
|
cancel-in-progress: false # Wait for current run to finish
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
train:
|
|
if: ${{ vars.ENABLE_RL_TRAINING == 'true' }}
|
|
name: Train RL Model
|
|
runs-on: ${{ fromJSON(vars.HETZNER_FLEET_ONLINE == 'false' && '["ubuntu-24.04"]' || '["self-hosted","hetzner-robot"]') }}
|
|
timeout-minutes: 360 # 6 hours max
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
cache: 'pip'
|
|
cache-dependency-path: 'packages/training/scripts/rl/requirements.txt'
|
|
|
|
- name: Install dependencies
|
|
working-directory: packages/training/scripts/rl
|
|
run: |
|
|
pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install -e .
|
|
|
|
- name: Verify installation
|
|
run: |
|
|
python --version
|
|
pip list | grep -E "(art|asyncpg)"
|
|
|
|
- name: Check training readiness
|
|
id: readiness
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
run: |
|
|
python -c "
|
|
import asyncio
|
|
import asyncpg
|
|
import os
|
|
import sys
|
|
|
|
async def check():
|
|
try:
|
|
pool = await asyncpg.create_pool(
|
|
os.getenv('DATABASE_URL'),
|
|
min_size=1,
|
|
max_size=2,
|
|
timeout=30
|
|
)
|
|
|
|
# Count scored trajectories ready for training
|
|
count = await pool.fetchval('''
|
|
SELECT COUNT(*) FROM trajectories
|
|
WHERE \"isTrainingData\" = true
|
|
AND \"usedInTraining\" = false
|
|
AND \"aiJudgeReward\" IS NOT NULL
|
|
AND \"stepsJson\" IS NOT NULL
|
|
AND \"stepsJson\"::text != 'null'
|
|
AND \"stepsJson\"::text != '[]'
|
|
''')
|
|
|
|
print(f'✅ Database connected')
|
|
print(f'📊 Trajectories ready for training: {count}')
|
|
|
|
# Need 100 bundles minimum
|
|
min_required = 100
|
|
ready = count >= min_required
|
|
|
|
if ready:
|
|
print(f'✅ READY: {count} >= {min_required}')
|
|
else:
|
|
print(f'⏳ NOT READY: {count} < {min_required} (need {min_required - count} more)')
|
|
|
|
with open(os.getenv('GITHUB_OUTPUT'), 'a') as f:
|
|
f.write(f'ready={str(ready).lower()}\\n')
|
|
f.write(f'count={count}\\n')
|
|
|
|
await pool.close()
|
|
|
|
except Exception as e:
|
|
print(f'❌ Error checking readiness: {e}', file=sys.stderr)
|
|
with open(os.getenv('GITHUB_OUTPUT'), 'a') as f:
|
|
f.write('ready=false\\n')
|
|
f.write('count=0\\n')
|
|
sys.exit(1)
|
|
|
|
asyncio.run(check())
|
|
"
|
|
|
|
- name: Get batch info
|
|
id: batch
|
|
env:
|
|
# From workflow_dispatch inputs:
|
|
BATCH_ID_INPUT: ${{ inputs.batch_id || '' }}
|
|
WINDOW_ID_INPUT: ${{ inputs.window_id || '' }}
|
|
FORCE_INPUT: ${{ inputs.force || 'false' }}
|
|
BASE_MODEL_INPUT: ${{ inputs.base_model || '' }}
|
|
# From repository_dispatch payload:
|
|
BATCH_ID_PAYLOAD: ${{ github.event.client_payload.batch_id || '' }}
|
|
WINDOW_ID_PAYLOAD: ${{ github.event.client_payload.window_id || '' }}
|
|
FORCE_PAYLOAD: ${{ github.event.client_payload.force || 'false' }}
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
run: |
|
|
# Determine batch_id
|
|
if [ -n "$BATCH_ID_PAYLOAD" ]; then
|
|
echo "batch_id=$BATCH_ID_PAYLOAD" >> $GITHUB_OUTPUT
|
|
elif [ -n "$BATCH_ID_INPUT" ]; then
|
|
echo "batch_id=$BATCH_ID_INPUT" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "batch_id=batch-$(date +%s)" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Determine window_id
|
|
if [ -n "$WINDOW_ID_PAYLOAD" ]; then
|
|
echo "window_id=$WINDOW_ID_PAYLOAD" >> $GITHUB_OUTPUT
|
|
elif [ -n "$WINDOW_ID_INPUT" ]; then
|
|
echo "window_id=$WINDOW_ID_INPUT" >> $GITHUB_OUTPUT
|
|
else
|
|
WINDOW=$(date -u +"%Y-%m-%dT%H:00")
|
|
echo "window_id=$WINDOW" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Determine force flag
|
|
if [ "$FORCE_PAYLOAD" = "true" ] || [ "$FORCE_INPUT" = "true" ]; then
|
|
echo "force=true" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "force=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Determine base model
|
|
if [ -n "$BASE_MODEL_INPUT" ]; then
|
|
echo "base_model=$BASE_MODEL_INPUT" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "base_model=$FEED_DEFAULT_BASE_MODEL" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Generate model version
|
|
MODEL_VERSION=$(python -c "
|
|
import asyncio
|
|
import asyncpg
|
|
import os
|
|
import sys
|
|
|
|
async def get_version():
|
|
try:
|
|
pool = await asyncpg.create_pool(os.getenv('DATABASE_URL'), timeout=10)
|
|
|
|
latest = await pool.fetchval('''
|
|
SELECT version FROM trained_models
|
|
WHERE status IN ('ready', 'deployed')
|
|
ORDER BY \"createdAt\" DESC
|
|
LIMIT 1
|
|
''')
|
|
|
|
if latest:
|
|
parts = latest.strip('v').split('.')
|
|
patch = int(parts[2]) + 1
|
|
version = f'v{parts[0]}.{parts[1]}.{patch}'
|
|
else:
|
|
version = 'v1.0.0'
|
|
|
|
print(version)
|
|
await pool.close()
|
|
|
|
except Exception as e:
|
|
import time
|
|
version = f'v1.0.{int(time.time()) % 10000}'
|
|
print(version)
|
|
|
|
asyncio.run(get_version())
|
|
" 2>/dev/null)
|
|
|
|
echo "model_version=$MODEL_VERSION" >> $GITHUB_OUTPUT
|
|
echo "source=github_cron" >> $GITHUB_OUTPUT
|
|
|
|
- name: Skip if not ready (unless forced)
|
|
if: steps.readiness.outputs.ready != 'true' && steps.batch.outputs.force != 'true'
|
|
run: |
|
|
echo "⏭️ Not ready for training and force=false"
|
|
echo "Trajectories: ${{ steps.readiness.outputs.count }}"
|
|
echo "Required: 100 (minimum bundles)"
|
|
exit 0
|
|
|
|
- name: Update batch status to training
|
|
if: steps.readiness.outputs.ready == 'true' || steps.batch.outputs.force == 'true'
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
BATCH_ID: ${{ steps.batch.outputs.batch_id }}
|
|
run: |
|
|
python -c "
|
|
import asyncio
|
|
import asyncpg
|
|
import os
|
|
|
|
async def update():
|
|
pool = await asyncpg.create_pool(os.getenv('DATABASE_URL'))
|
|
batch_id = os.getenv('BATCH_ID')
|
|
|
|
async with pool.acquire() as conn:
|
|
async with conn.transaction():
|
|
await conn.execute('''
|
|
INSERT INTO training_batches (
|
|
\"batchId\", id, status, \"startedAt\", \"createdAt\"
|
|
) VALUES (
|
|
\$1, \$1, 'training', NOW(), NOW()
|
|
)
|
|
ON CONFLICT (\"batchId\")
|
|
DO UPDATE SET status = 'training', \"startedAt\" = NOW()
|
|
''', batch_id)
|
|
|
|
print(f'✅ Batch {batch_id} status: training')
|
|
await pool.close()
|
|
|
|
asyncio.run(update())
|
|
"
|
|
|
|
- name: Select base model
|
|
id: model_selection
|
|
if: steps.readiness.outputs.ready == 'true' || steps.batch.outputs.force == 'true'
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
BASE_MODEL_OVERRIDE: ${{ steps.batch.outputs.base_model }}
|
|
run: |
|
|
python -c "
|
|
import asyncio
|
|
import asyncpg
|
|
import os
|
|
import sys
|
|
|
|
async def select_model():
|
|
try:
|
|
pool = await asyncpg.create_pool(os.getenv('DATABASE_URL'))
|
|
base_model_override = os.getenv('BASE_MODEL_OVERRIDE', '')
|
|
default_base_model = os.getenv('FEED_DEFAULT_BASE_MODEL', 'google/gemma-4-E4B-it')
|
|
|
|
# Count training bundles
|
|
bundle_count = await pool.fetchval('''
|
|
SELECT COUNT(*) FROM trajectories
|
|
WHERE \"isTrainingData\" = true
|
|
AND \"usedInTraining\" = false
|
|
AND \"aiJudgeReward\" IS NOT NULL
|
|
AND \"stepsJson\" IS NOT NULL
|
|
AND \"stepsJson\"::text != 'null'
|
|
AND \"stepsJson\"::text != '[]'
|
|
''')
|
|
|
|
print(f'📊 Bundle count: {bundle_count}')
|
|
|
|
# Use override if provided, otherwise use default
|
|
if base_model_override:
|
|
base_model = base_model_override
|
|
strategy = 'override'
|
|
else:
|
|
base_model = default_base_model
|
|
strategy = 'default'
|
|
|
|
print(f'📦 Selected model: {base_model}')
|
|
print(f'📋 Strategy: {strategy}')
|
|
|
|
with open(os.getenv('GITHUB_OUTPUT'), 'a') as f:
|
|
f.write(f'base_model={base_model}\\n')
|
|
f.write(f'strategy={strategy}\\n')
|
|
f.write(f'bundle_count={bundle_count}\\n')
|
|
|
|
await pool.close()
|
|
|
|
except Exception as e:
|
|
print(f'❌ Model selection failed: {e}', file=sys.stderr)
|
|
default_base_model = os.getenv('FEED_DEFAULT_BASE_MODEL', 'google/gemma-4-E4B-it')
|
|
with open(os.getenv('GITHUB_OUTPUT'), 'a') as f:
|
|
f.write(f'base_model={default_base_model}\\n')
|
|
f.write('strategy=fallback\\n')
|
|
sys.exit(1)
|
|
|
|
asyncio.run(select_model())
|
|
"
|
|
|
|
- name: Run RL Training
|
|
id: training
|
|
if: steps.readiness.outputs.ready == 'true' || steps.batch.outputs.force == 'true'
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
BATCH_ID: ${{ steps.batch.outputs.batch_id }}
|
|
WINDOW_ID: ${{ steps.batch.outputs.window_id }}
|
|
MODEL_VERSION: ${{ steps.batch.outputs.model_version }}
|
|
BASE_MODEL: ${{ steps.model_selection.outputs.base_model }}
|
|
MODE: single
|
|
MAX_EXAMPLES: "2000"
|
|
MAX_STEPS_PER_TRAJECTORY: "20"
|
|
MAX_SEQ_LENGTH: "8192"
|
|
working-directory: packages/training/scripts/rl
|
|
run: |
|
|
echo "🚀 Starting training"
|
|
echo "Batch ID: $BATCH_ID"
|
|
echo "Window ID: $WINDOW_ID"
|
|
echo "Model Version: $MODEL_VERSION"
|
|
echo "Strategy: ${{ steps.model_selection.outputs.strategy }}"
|
|
echo "Base Model: $BASE_MODEL"
|
|
echo "Trajectories available: ${{ steps.readiness.outputs.count }}"
|
|
echo "Bundle count: ${{ steps.model_selection.outputs.bundle_count }}"
|
|
|
|
# Run trainer
|
|
python src/training/feed_trainer.py
|
|
|
|
- name: Update batch status to completed
|
|
if: success() && (steps.readiness.outputs.ready == 'true' || steps.batch.outputs.force == 'true')
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
BATCH_ID: ${{ steps.batch.outputs.batch_id }}
|
|
run: |
|
|
python -c "
|
|
import asyncio
|
|
import asyncpg
|
|
import os
|
|
|
|
async def update():
|
|
pool = await asyncpg.create_pool(os.getenv('DATABASE_URL'))
|
|
batch_id = os.getenv('BATCH_ID')
|
|
|
|
await pool.execute('''
|
|
UPDATE training_batches
|
|
SET status = 'completed', \"completedAt\" = NOW()
|
|
WHERE \"batchId\" = \$1
|
|
''', batch_id)
|
|
|
|
print(f'✅ Batch {batch_id} completed')
|
|
await pool.close()
|
|
|
|
asyncio.run(update())
|
|
"
|
|
|
|
- name: Update batch status to failed
|
|
if: failure() && (steps.readiness.outputs.ready == 'true' || steps.batch.outputs.force == 'true')
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
BATCH_ID: ${{ steps.batch.outputs.batch_id }}
|
|
run: |
|
|
python -c "
|
|
import asyncio
|
|
import asyncpg
|
|
import os
|
|
|
|
async def update():
|
|
pool = await asyncpg.create_pool(os.getenv('DATABASE_URL'))
|
|
batch_id = os.getenv('BATCH_ID')
|
|
|
|
await pool.execute('''
|
|
UPDATE training_batches
|
|
SET status = 'failed', error = 'GitHub Actions workflow failed'
|
|
WHERE \"batchId\" = \$1
|
|
''', batch_id)
|
|
|
|
print(f'❌ Batch {batch_id} failed')
|
|
await pool.close()
|
|
|
|
asyncio.run(update())
|
|
"
|
|
|
|
- name: Upload training logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
with:
|
|
name: training-logs-${{ steps.batch.outputs.batch_id }}
|
|
path: |
|
|
packages/training/scripts/rl/logs/
|
|
packages/training/scripts/rl/*.log
|
|
retention-days: 7
|
|
|
|
- name: Report status
|
|
if: always()
|
|
run: |
|
|
echo "Training Status: ${{ job.status }}"
|
|
echo "Batch ID: ${{ steps.batch.outputs.batch_id }}"
|
|
echo "Window ID: ${{ steps.batch.outputs.window_id }}"
|
|
echo "Ready: ${{ steps.readiness.outputs.ready }}"
|
|
echo "Trajectories: ${{ steps.readiness.outputs.count }}"
|
|
echo "Model: ${{ steps.model_selection.outputs.base_model }}"
|