44# Probes the system for Python, GPU backends, and installs the minimum
55# viable stack. Called by Aegis skill-runtime-manager during installation.
66#
7+ # Uses skills/lib/env_config.py for hardware detection and model optimization.
8+ #
79# Exit codes:
810# 0 = success
911# 1 = fatal error (no Python found and cannot install)
@@ -13,6 +15,7 @@ set -euo pipefail
1315
1416SKILL_DIR=" $( cd " $( dirname " $0 " ) " && pwd) "
1517VENV_DIR=" $SKILL_DIR /.venv"
18+ LIB_DIR=" $( cd " $SKILL_DIR /../../lib" 2> /dev/null && pwd || echo " " ) "
1619LOG_PREFIX=" [YOLO-2026-deploy]"
1720
1821log () { echo " $LOG_PREFIX $* " >&2 ; }
@@ -21,7 +24,6 @@ emit() { echo "$1"; } # JSON to stdout for Aegis to parse
2124# ─── Step 1: Find or install Python ─────────────────────────────────────────
2225
2326find_python () {
24- # Check common Python 3 locations
2527 for cmd in python3.12 python3.11 python3.10 python3.9 python3; do
2628 if command -v " $cmd " & > /dev/null; then
2729 local ver
@@ -36,7 +38,6 @@ find_python() {
3638 fi
3739 done
3840
39- # Check conda
4041 if command -v conda & > /dev/null; then
4142 log " No system Python >=3.9 found, but conda is available"
4243 log " Creating conda environment..."
@@ -48,7 +49,6 @@ find_python() {
4849 return 0
4950 fi
5051
51- # Check pyenv
5252 if command -v pyenv & > /dev/null; then
5353 log " No system Python >=3.9 found, using pyenv..."
5454 pyenv install -s 3.11.9
@@ -76,55 +76,60 @@ if [ ! -d "$VENV_DIR" ]; then
7676 " $PYTHON_CMD " -m venv " $VENV_DIR "
7777fi
7878
79- # Activate venv
8079# shellcheck disable=SC1091
8180source " $VENV_DIR /bin/activate"
8281PIP=" $VENV_DIR /bin/pip"
8382
84- # Upgrade pip
8583" $PIP " install --upgrade pip -q 2> /dev/null || true
8684
8785emit ' {"event": "progress", "stage": "venv", "message": "Virtual environment ready"}'
8886
89- # ─── Step 3: Detect compute backend ──────── ─────────────────────────────────
87+ # ─── Step 3: Detect hardware via env_config ─────────────────────────────────
9088
9189BACKEND=" cpu"
9290
93- detect_gpu () {
94- # NVIDIA CUDA
91+ if [ -n " $LIB_DIR " ] && [ -f " $LIB_DIR /env_config.py" ]; then
92+ log " Detecting hardware via env_config.py..."
93+ DETECT_OUTPUT=$( " $VENV_DIR /bin/python" -c "
94+ import sys
95+ sys.path.insert(0, '$LIB_DIR ')
96+ from env_config import HardwareEnv
97+ env = HardwareEnv.detect()
98+ print(env.backend)
99+ " 2>&1 ) || true
100+
101+ # The last line of output is the backend name
102+ BACKEND=$( echo " $DETECT_OUTPUT " | tail -1)
103+
104+ # Validate backend value
105+ case " $BACKEND " in
106+ cuda|rocm|mps|intel|cpu) ;;
107+ * )
108+ log " env_config returned unexpected backend '$BACKEND ', falling back to heuristic"
109+ BACKEND=" cpu"
110+ ;;
111+ esac
112+
113+ log " env_config detected backend: $BACKEND "
114+ else
115+ log " env_config.py not found, using heuristic detection..."
116+
117+ # Fallback: inline GPU detection (same as before)
95118 if command -v nvidia-smi & > /dev/null; then
96- local cuda_ver
97119 cuda_ver=$( nvidia-smi --query-gpu=driver_version --format=csv,noheader 2> /dev/null | head -1)
98120 if [ -n " $cuda_ver " ]; then
99121 BACKEND=" cuda"
100122 log " Detected NVIDIA GPU (driver: $cuda_ver )"
101- return 0
102123 fi
103- fi
104-
105- # AMD ROCm
106- if command -v rocm-smi & > /dev/null || [ -d " /opt/rocm" ]; then
124+ elif command -v rocm-smi & > /dev/null || [ -d " /opt/rocm" ]; then
107125 BACKEND=" rocm"
108126 log " Detected AMD ROCm"
109- return 0
110- fi
111-
112- # Apple Silicon MPS
113- if [ " $( uname) " = " Darwin" ]; then
114- local arch
115- arch=$( uname -m)
116- if [ " $arch " = " arm64" ]; then
117- BACKEND=" mps"
118- log " Detected Apple Silicon (MPS)"
119- return 0
120- fi
127+ elif [ " $( uname) " = " Darwin" ] && [ " $( uname -m) " = " arm64" ]; then
128+ BACKEND=" mps"
129+ log " Detected Apple Silicon (MPS)"
121130 fi
131+ fi
122132
123- log " No GPU detected, using CPU backend"
124- return 0
125- }
126-
127- detect_gpu
128133emit " {\" event\" : \" progress\" , \" stage\" : \" gpu\" , \" backend\" : \" $BACKEND \" , \" message\" : \" Compute backend: $BACKEND \" }"
129134
130135# ─── Step 4: Install requirements ────────────────────────────────────────────
@@ -142,39 +147,50 @@ emit "{\"event\": \"progress\", \"stage\": \"install\", \"message\": \"Installin
142147
143148" $PIP " install -r " $REQ_FILE " -q 2>&1 | tail -5 >&2
144149
145- # ─── Step 5: CoreML pre-conversion (MPS only) ──── ───────────────────────────
150+ # ─── Step 5: Pre-convert model to optimized format ───────────────────────────
146151
147- if [ " $BACKEND " = " mps " ]; then
148- log " Pre-converting default model to CoreML for ANE acceleration ..."
149- emit ' { "event": "progress", "stage": "coreml ", "message": "Converting model to CoreML (~30s )..."} '
152+ if [ " $BACKEND " ! = " cpu " ] || [ -f " $SKILL_DIR /requirements_cpu.txt " ]; then
153+ log " Pre-converting model to optimized format for $BACKEND ..."
154+ emit " { \ " event\ " : \ " progress\ " , \ " stage\ " : \" optimize \ " , \ " message\ " : \ " Converting model for $BACKEND (~30-120s )...\" } "
150155
151156 " $VENV_DIR /bin/python" -c "
152- from ultralytics import YOLO
153- model = YOLO('yolo26n.pt')
154- exported = model.export(format='coreml', half=True, nms=False)
155- print(f'CoreML model exported: {exported}')
157+ import sys
158+ sys.path.insert(0, '$LIB_DIR ')
159+ from env_config import HardwareEnv
160+ env = HardwareEnv.detect()
161+
162+ if env.framework_ok:
163+ from ultralytics import YOLO
164+ model = YOLO('yolo26n.pt')
165+ result = env.export_model(model, 'yolo26n')
166+ if result:
167+ print(f'Optimized model exported: {result}')
168+ else:
169+ print('Export skipped or failed — will use PyTorch at runtime')
170+ else:
171+ print(f'Optimized runtime not available for {env.backend} — will use PyTorch')
156172" 2>&1 | while read -r line; do log " $line " ; done
157173
158174 if [ $? -eq 0 ]; then
159- emit ' { "event": "progress", "stage": "coreml ", "message": "CoreML conversion complete"} '
175+ emit " { \ " event\ " : \ " progress\ " , \ " stage\ " : \" optimize \ " , \ " message\ " : \" Model optimization complete\" } "
160176 else
161- log " WARNING: CoreML conversion failed, will use PyTorch MPS at runtime"
162- emit ' { "event": "progress", "stage": "coreml ", "message": "CoreML conversion failed — PyTorch MPS fallback"} '
177+ log " WARNING: Model optimization failed, will use PyTorch at runtime"
178+ emit " { \ " event\ " : \ " progress\ " , \ " stage\ " : \" optimize \ " , \ " message\ " : \" Optimization failed — PyTorch fallback\" } "
163179 fi
164180fi
165181
166182# ─── Step 6: Verify installation ────────────────────────────────────────────
167183
168184log " Verifying installation..."
169185" $VENV_DIR /bin/python" -c "
170- from ultralytics import YOLO
171- import torch
172- device = 'cpu'
173- if torch.cuda.is_available(): device = 'cuda'
174- elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available(): device = 'mps'
175- print(f'OK: ultralytics loaded, torch device={device}')
186+ import sys
187+ sys.path.insert(0, '$LIB_DIR ')
188+ from env_config import HardwareEnv
189+ import json
190+
191+ env = HardwareEnv.detect()
192+ print(json.dumps(env.to_dict(), indent=2))
176193" 2>&1 | while read -r line; do log " $line " ; done
177194
178195emit " {\" event\" : \" complete\" , \" backend\" : \" $BACKEND \" , \" message\" : \" YOLO 2026 skill installed ($BACKEND backend)\" }"
179196log " Done! Backend: $BACKEND "
180-
0 commit comments