Spaces:
Runtime error
Runtime error
| # ───────────────────────────────────────────────────────────────────────────── | |
| # ChessEcon Docker Entrypoint | |
| # | |
| # Modes (CMD argument): | |
| # backend — Start the FastAPI server (default) | |
| # train — Run the RL training loop | |
| # selfplay — Run self-play data collection only (no training) | |
| # download — Download the HuggingFace model and exit | |
| # demo — Run a quick 3-game demo and exit | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| set -euo pipefail | |
| MODE="${1:-backend}" | |
| echo "╔══════════════════════════════════════════════════════════════╗" | |
| echo "║ ChessEcon — Multi-Agent Chess RL ║" | |
| echo "║ TextArena + Meta OpenEnv + GRPO | Hackathon 2026 ║" | |
| echo "╚══════════════════════════════════════════════════════════════╝" | |
| echo "" | |
| echo "Mode: $MODE" | |
| echo "Model: ${PLAYER_MODEL:-Qwen/Qwen2.5-0.5B-Instruct}" | |
| echo "RL Method: ${RL_METHOD:-grpo}" | |
| echo "" | |
| # ── Validate required environment variables ─────────────────────────────── | |
| check_env() { | |
| local var_name="$1" | |
| local required="${2:-false}" | |
| if [ -z "${!var_name:-}" ]; then | |
| if [ "$required" = "true" ]; then | |
| echo "ERROR: Required environment variable $var_name is not set." | |
| echo " Please set it in your .env file or Docker environment." | |
| exit 1 | |
| else | |
| echo "WARNING: Optional variable $var_name is not set." | |
| fi | |
| fi | |
| } | |
| # Always required | |
| check_env "HF_TOKEN" "true" | |
| # Required for Claude coaching | |
| if [ "${ENABLE_CLAUDE_COACHING:-true}" = "true" ]; then | |
| check_env "ANTHROPIC_API_KEY" "true" | |
| fi | |
| # ── Download model from HuggingFace if not cached ──────────────────────── | |
| MODEL_NAME="${PLAYER_MODEL:-Qwen/Qwen2.5-0.5B-Instruct}" | |
| MODEL_CACHE_DIR="/app/models/$(echo $MODEL_NAME | tr '/' '_')" | |
| if [ ! -d "$MODEL_CACHE_DIR" ] || [ "${FORCE_DOWNLOAD:-false}" = "true" ]; then | |
| echo "Downloading model: $MODEL_NAME" | |
| echo "Cache directory: $MODEL_CACHE_DIR" | |
| python3 -c " | |
| from huggingface_hub import snapshot_download | |
| import os | |
| snapshot_download( | |
| repo_id='${MODEL_NAME}', | |
| local_dir='${MODEL_CACHE_DIR}', | |
| token=os.environ.get('HF_TOKEN'), | |
| ignore_patterns=['*.bin', '*.pt'] if os.environ.get('USE_SAFETENSORS', 'true') == 'true' else [] | |
| ) | |
| print('Model downloaded successfully.') | |
| " | |
| echo "Model ready at: $MODEL_CACHE_DIR" | |
| else | |
| echo "Model already cached at: $MODEL_CACHE_DIR" | |
| fi | |
| export MODEL_LOCAL_PATH="$MODEL_CACHE_DIR" | |
| # ── Execute the requested mode ──────────────────────────────────────────── | |
| case "$MODE" in | |
| backend) | |
| echo "" | |
| echo "Starting ChessEcon API server on port ${PORT:-8000}..." | |
| echo "Dashboard: http://localhost:${PORT:-8000}" | |
| echo "API docs: http://localhost:${PORT:-8000}/docs" | |
| echo "WebSocket: ws://localhost:${PORT:-8000}/ws" | |
| echo "" | |
| exec python3 -m uvicorn backend.main:app \ | |
| --host 0.0.0.0 \ | |
| --port "${PORT:-8000}" \ | |
| --workers "${WORKERS:-1}" \ | |
| --log-level "${LOG_LEVEL:-info}" | |
| ;; | |
| train) | |
| echo "" | |
| echo "Starting RL training..." | |
| echo "Method: ${RL_METHOD:-grpo}" | |
| echo "Games per batch: ${GAMES_PER_BATCH:-8}" | |
| echo "Training steps: ${MAX_TRAINING_STEPS:-1000}" | |
| echo "" | |
| exec python3 -m training.run \ | |
| --method "${RL_METHOD:-grpo}" \ | |
| --model-path "$MODEL_LOCAL_PATH" \ | |
| --games-per-batch "${GAMES_PER_BATCH:-8}" \ | |
| --max-steps "${MAX_TRAINING_STEPS:-1000}" \ | |
| --output-dir "/app/data/training" \ | |
| --log-dir "/app/logs" | |
| ;; | |
| selfplay) | |
| echo "" | |
| echo "Starting self-play data collection..." | |
| echo "Games: ${SELFPLAY_GAMES:-100}" | |
| echo "" | |
| exec python3 -m training.run \ | |
| --method selfplay \ | |
| --model-path "$MODEL_LOCAL_PATH" \ | |
| --games "${SELFPLAY_GAMES:-100}" \ | |
| --output-dir "/app/data/games" | |
| ;; | |
| download) | |
| echo "Model download complete. Exiting." | |
| exit 0 | |
| ;; | |
| demo) | |
| echo "" | |
| echo "Running 3-game demo..." | |
| exec python3 -c " | |
| import asyncio | |
| import sys | |
| sys.path.insert(0, '/app') | |
| from backend.chess.engine import ChessEngine | |
| from backend.economy.ledger import EconomicConfig, WalletManager, TournamentOrganizer | |
| async def run_demo(): | |
| config = EconomicConfig() | |
| wallets = WalletManager(config) | |
| wallets.create_wallet('white', 100.0) | |
| wallets.create_wallet('black', 100.0) | |
| organizer = TournamentOrganizer(config, wallets) | |
| for game_num in range(1, 4): | |
| print(f'\n--- Game {game_num} ---') | |
| engine = ChessEngine() | |
| game_id = organizer.open_game('white', 'black') | |
| print(f'Game ID: {game_id}') | |
| print(f'Prize pool: {organizer.games[game_id].prize_pool}') | |
| move_count = 0 | |
| while not engine.is_game_over() and move_count < 20: | |
| legal = engine.get_legal_moves() | |
| if not legal: | |
| break | |
| import random | |
| move = random.choice(legal) | |
| engine.make_move(move) | |
| move_count += 1 | |
| result = engine.get_result() or '1/2-1/2' | |
| winner = 'white' if result == '1-0' else ('black' if result == '0-1' else None) | |
| payout = organizer.close_game(game_id, winner) | |
| print(f'Result: {result} | White: {payout[\"white\"]:.1f} | Black: {payout[\"black\"]:.1f}') | |
| print(f'Wallets — White: {wallets.get_balance(\"white\"):.1f} | Black: {wallets.get_balance(\"black\"):.1f}') | |
| print('\nDemo complete.') | |
| asyncio.run(run_demo()) | |
| " | |
| ;; | |
| *) | |
| echo "Unknown mode: $MODE" | |
| echo "Valid modes: backend | train | selfplay | download | demo" | |
| exit 1 | |
| ;; | |
| esac | |