Compare commits

...

2 Commits

Author SHA1 Message Date
shawn ecda528b90 build:hermes-runtime — Portable Hermes Agent package for USB
Build scripts, configuration, and launch scripts for the Hermes Portable
Rescue runtime.

Includes:
- build-hermes-runtime.sh — Main build script (venv, config, tools)
- bootstrap-venv.sh — Standalone Python venv builder
- config.yaml — Agent configuration for rescue mode
- launch.sh/launch.ps1 — Linux and Windows entry points
- agent/hermes — Entry point for autorun.sh compatibility
- tool-manifest.json — Tool registry for diagnostic binaries
- scripts/ — check-env.sh, mount-storage.sh helpers
- autorun.inf — Windows USB auto-launch
- README — Build and runtime documentation

Part of the Hermes Portable Rescue project board.
Task: t_c816e37d
2026-07-04 03:59:06 -04:00
shawn 85d460e749 research: LLM strategy deliverable — hybrid local-first recommendation 2026-07-04 03:54:02 -04:00
13 changed files with 919 additions and 88 deletions
+347
View File
@@ -0,0 +1,347 @@
# LLM Strategy — Hermes Portable Rescue
**Task:** T2 — research:llm-strat
**Author:** Ashley (assistant)
**Date:** 2026-07-04
**Status:** Complete — ready for T3 (workstream:arch-decision)
---
## Executive Summary
**Recommended approach: Hybrid Local-First strategy**
| Layer | Model | When | Storage | RAM |
|-------|-------|------|---------|-----|
| **Primary** | Qwen2.5-7B-Instruct Q4_K_M (4.4 GB) | Always, offline-first | USB | ~7 GB |
| **Lightweight fallback** | Qwen2.5-3B-Instruct Q4_K_M (2.0 GB) | Target < 8 GB RAM | USB | ~4 GB |
| **API upgrade** | Cloud API (OpenCode Go / DeepSeek-V4) | Network available, complex reasoning | — | — |
**Total USB storage for models:** 4.4 GB (primary) + 2.0 GB (fallback) = 6.4 GB
**Recommended USB drive size:** 64 GB+
---
## 1. Options Evaluated
### 1A — Local GGUF Model (Offline-only)
**How it works:** Ship a pre-quantized GGUF model on the USB drive. On boot, launch `llama-server` (single binary, ~12 MB statically compiled) which provides an OpenAI-compatible API on localhost. Hermes agent connects to it as its provider.
**Best for:**
- PCs with no working network (broken WiFi driver, dead NIC, paywalled captive portal)
- Air-gapped / sensitive environments
- Consistent, predictable performance
**LLM backpack (llama-server):** 12 MB static binary, no Python deps, no shared libraries.
**Risks:**
- Slower than API (10-40 tok/s on CPU vs 50-200+ tok/s via API)
- Model size matters — must fit in available RAM after OS overhead
- Cannot use large models (70B is out of reach on consumer hardware)
### 1B — API-based (Cloud-only)
**How it works:** The rescue agent requires a working internet connection and calls a cloud API (OpenCode Go, OpenRouter, Anthropic, etc.) for every inference.
**Best for:**
- Complex multi-factor reasoning (BSOD chain analysis, rare hardware issues)
- Access to state-of-the-art models (Claude, GPT-4, DeepSeek-V4-Pro)
- Minimal USB storage overhead (no model files)
**Risks:**
- **Crippling failure mode** — The most common reason for a rescue USB is a PC with broken network. If the agent can't think without the API, it's useless when you need it most.
- Latency on spotty/paywalled connections
- Privacy — diagnostic data leaves the target machine
- API cost per session
### 1C — Hybrid (Local-First, API-Enhanced)
**How it works:** Ship both a local GGUF model and an API config. Default to local inference. If a working internet connection is detected AND the local model's output is low-confidence (or the task is complex enough), fall through to a cloud API call for the specific step.
**Best for:**
- Everything — offline-capable by default, enhanced when possible
- Graceful degradation: works on a PC with a fried NIC, gets *better* on a PC with working WiFi
**Risks:**
- Slightly larger USB image (model files + API config)
- Two code paths to test and maintain
- Need a heuristic for "when to escalate to API"
---
## 2. Model Comparison
### Candidate Models for Local Inference
| Model | Size (params) | Q4_K_M Size | RAM Needed | Speed (CPU) | Technical Reasoning |
|-------|--------------|-------------|------------|-------------|-------------------|
| **Qwen2.5-7B-Instruct** | 7.6B | **4.4 GB** | ~7 GB | 15-25 tok/s | Excellent — strong instruction following |
| Qwen2.5-Coder-7B-Instruct | 7.6B | 4.4 GB | ~7 GB | 15-25 tok/s | Great for code, overfit for general repair |
| Llama-3.2-3B-Instruct | 3.2B | **1.9 GB** | ~4 GB | 25-40 tok/s | Good for simple diagnostics, weak at chains |
| Qwen2.5-3B-Instruct | 3.1B | **2.0 GB** | ~4 GB | 25-40 tok/s | Good-small model, decent reasoning |
| Phi-3.5-mini-Instruct | 3.8B | 2.2 GB | ~5 GB | 20-35 tok/s | Decent technical, smaller tokenizer |
| Mistral-7B-v0.3 | 7.3B | 4.4 GB | ~7 GB | 13-23 tok/s | Strong general, slower than Qwen |
| Gemma-2-9B | 9.2B | 5.5 GB | ~8 GB | 10-18 tok/s | Very strong, but needs more RAM |
### Recommended: Qwen2.5-7B-Instruct Q4_K_M
**Why this model:**
1. **Best quality-to-size ratio** — Tops the Open LLM Leaderboard in the 7B class, with strong instruction following and reasoning. Perfect for parsing minidump stop codes, SMART attribute values, and multi-step diagnostic chains.
2. **Qwen2.5 tokenizer** handles technical English and hex dumps efficiently.
3. **Q4_K_M** is the sweet spot — only 1.7% perplexity loss vs FP16, 4x smaller than FP16.
4. **Single file** — 4.36 GB for Q4_K_M, no shards to manage.
**Why NOT Qwen2.5-Coder-7B:**
- Overfitted for code generation. A rescue agent needs to reason about stop codes, hardware specs, and repair procedures — not generate code. The general instruct variant handles these better.
### Lightweight Fallback: Qwen2.5-3B-Instruct Q4_K_M
- 1.96 GB on disk, runs in ~4 GB RAM
- Covers PCs with only 8 GB total RAM (OS overhead + Hermes leaves ~4-5 GB for the model)
- Speedy: 25-40 tok/s on modern CPU
### RAM Calculation for Target PCs
| Target PC RAM | OS Overhead (Live Linux) | Available for Model | Can Run 7B Q4_K_M? | Can Run 3B Q4_K_M? |
|--------------|--------------------------|-------------------|--------------------|--------------------|
| 4 GB | ~1-1.5 GB | ~2.5-3 GB | No | **Tight** |
| 8 GB | ~1-1.5 GB | ~6.5-7 GB | **Yes** | Yes |
| 16 GB | ~1-1.5 GB | ~14.5-15 GB | Yes | Yes |
| 32 GB | ~1-1.5 GB | ~30 GB | Yes | Yes |
**Conclusion:** The 7B Q4_K_M model requires at least 8 GB of target PC RAM. This covers the vast majority of consumer and gaming PCs from the last 7 years. For low-RAM machines (< 8 GB), fall back to the 3B model.
---
## 3. Boot Environment Implications
The LLM strategy is **tightly coupled** with the boot environment (T1 — research:boot-env).
| Boot Env | LLM Viability | Notes |
|----------|--------------|-------|
| **Linux live** (Alpine, Arch, custom) | ✅ **Excellent** | Native llama.cpp binary, full RAM access, Python with pip, simple process management |
| **WinPE** | ❌ **Impractical** | No native llama.cpp without MSVC runtime. No Python unless embedded. WinPE is heavily stripped (no .NET, no VC++ redist). Running LLM inference requires significant hacks. |
| **Ventoy + Linux ISO** | ✅ **Excellent** | Same as Linux live — llama.cpp + Python work natively |
**The LLM strategy effectively requires a Linux-based boot environment.** If the boot-env research (T1) concludes WinPE must be used, this strategy must be revised significantly (likely API-only with offline fallback being extremely limited diagnostic scripts without LLM).
**Assuming Linux live environment:**
```
Boot → Start llama-server in background → Start Hermes agent → Hermes connects to localhost:8080
[llama-server] [Hermes Agent]
┌──────────────┐ ┌──────────────┐
│ llama-server │←──localhost──│ Hermes core │
│ --port 8080 │ :8080/v1 │ provider: │
│ --ctx-size 4096│ │ base_url: │
│ model.gguf │ │ http:// │
└──────────────┘ │ localhost │
│ :8080/v1 │
└──────────────┘
```
---
## 4. Hermes Provider Configuration
Hermes supports **any OpenAI-compatible API** as a provider by setting `model.base_url` in config.yaml. For the rescue environment, the portable config would be:
```yaml
# ~/.hermes/config.yaml (rescue profile)
model:
# If local model is running:
base_url: http://localhost:8080/v1
api_key: "" # or "sk-no-key-required"
default: qwen2.5-7b-instruct-q4_k_m
provider: "" # base_url overrides provider
# If API fallback (network available):
# base_url: https://opencode.ai/zen/go/v1/
# api_key: ${OPENCODE_GO_API_KEY}
# default: deepseek-v4-flash
```
**Local inference stack:**
1. `llama-server` — single static binary (~12 MB), started as systemd service or background process
2. Launched with: `llama-server -m /usb/hermes/models/qwen2.5-7b-instruct-q4_k_m.gguf --host 127.0.0.1 --port 8080 --ctx-size 4096 --n-gpu-layers 0 --no-mmap`
3. Hermes agent detects the API is available, sets base_url, and works normally
**Key flag: `--no-mmap`** — Prevents memory-mapping the model file, important when running from a USB stick (mmap on slow USB can cause stuttering). Falls back to read-ahead loading.
---
## 5. Storage Budget on USB
| Component | Size | Notes |
|-----------|------|-------|
| Linux live OS + kernel | ~500 MB-1.5 GB | Alpine minimal ~200 MB, Arch ~800 MB, custom ~1 GB |
| llama-server binary | ~12 MB | Static build, no deps |
| Qwen2.5-7B Q4_K_M model | **4.4 GB** | Primary model |
| Qwen2.5-3B Q4_K_M model | **2.0 GB** | Fallback (optional) |
| Hermes agent (Python + venv) | ~200-400 MB | Minimal venv, hermes core |
| Diagnostic tools | ~100-300 MB | smartmontools, dmidecode, stress-ng, MemTest86, dd, etc. |
| Docker image cache | ~0 MB optional | Running native binaries preferred |
| **Total (with both models)** | **~7.5-9 GB** | |
**All models fit comfortably on a 32 GB USB stick.** For 64 GB+ sticks, consider adding additional quantizations or a larger model (e.g., Qwen2.5-14B Q4_K_M at ~8 GB for extra reasoning capability).
### USB Speed Impact
- USB 3.0 (5 Gbps) → ~500 MB/s theoretical
- USB 3.1 Gen 2 (10 Gbps) → ~1 GB/s
- USB 2.0 (480 Mbps) → ~60 MB/s practical
Model loading time at USB 3.0: ~9 seconds for 4.4 GB
Model loading at USB 2.0: ~75 seconds (annoying but acceptable — boot once per session)
**Recommendation:** Use `--no-mmap` flag with llama-server to avoid live USB I/O during inference. The model loads into RAM once at startup and operates entirely from RAM afterwards.
---
## 6. Speed & Latency Analysis
| Scenario | Task | Local 7B Q4_K_M | API (DeepSeek-V4-Flash) |
|----------|------|-----------------|------------------------|
| First token | Cold start | ~1-3 sec | ~0.5-2 sec |
| BSOD analysis | 200 tokens | ~10 sec | ~3-5 sec |
| SMART interpretation | 400 tokens | ~20 sec | ~5-8 sec |
| Repair plan | 800 tokens | ~35 sec | ~8-12 sec |
**Local inference is 2-5x slower than API** but still fast enough for a diagnostic workflow where the user waits for results anyway. BSOD analysis at 10 seconds is perfectly acceptable — the alternative is the user manually Googling stop codes.
---
## 7. Offline Capability Assessment
| Feature | Local GGUF | API-only | Hybrid |
|---------|-----------|----------|--------|
| Boot with dead NIC | ✅ | ❌ | ✅ |
| Boot in paywalled WiFi | ✅ | ❌ | ✅ |
| Boot in air-gapped env | ✅ | ❌ | ✅ |
| Complex reasoning | ⚠️ (7B is decent) | ✅ | ✅ (API when available) |
| BSOD analysis | ✅ | ✅ | ✅ |
| SMART disk parsing | ✅ | ✅ | ✅ |
| Driver download | ❌ (needs net) | ✅ | ⚠️ (net required anyway) |
| Backup to cloud | ❌ (needs net) | ✅ | ⚠️ (net required anyway) |
**Key insight:** The hybrid strategy handles the critical "PC won't boot / broken NIC" scenario that's the most common use case for a rescue USB. Both local and hybrid work offline for diagnostics. The only features that genuinely need a network (driver downloads, cloud backup) require it regardless of LLM strategy.
---
## 8. Model Download & Preparation
Models are downloaded during the build phase (not at runtime):
```bash
# Build script: fetch models
mkdir -p build/models/
# Primary model — Qwen2.5-7B-Instruct Q4_K_M
wget https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/resolve/main/qwen2.5-7b-instruct-q4_k_m.gguf \
-O build/models/qwen2.5-7b-instruct-q4_k_m.gguf
# Fallback model — Qwen2.5-3B-Instruct Q4_K_M
wget https://huggingface.co/Qwen/Qwen2.5-3B-Instruct-GGUF/resolve/main/qwen2.5-3b-instruct-q4_k_m.gguf \
-O build/models/qwen2.5-3b-instruct-q4_k_m.gguf
# llama-server binary (Linux x86_64 static)
wget https://github.com/ggml-org/llama.cpp/releases/latest/download/llama-server \
-O build/tools/llama-server
chmod +x build/tools/llama-server
```
**Licensing:** Qwen2.5 models use the Apache 2.0 license — free for commercial and personal use.
---
## 9. Implementation Recommendation
### Phase 1: Prototype (this sprint)
1. **Download** Qwen2.5-7B-Instruct Q4_K_M (4.4 GB) to the NAS project directory
2. **Build** a minimal Hermes profile ("rescue") with local provider config
3. **Package** llama-server binary
4. **Write** a startup script (launch-llm.sh) that:
- Probes available RAM
- Decides 7B vs 3B model based on free RAM
- Launches llama-server with appropriate flags
- Waits for API readiness
- Launches Hermes agent with local provider config
5. **Test** locally on Shawn's host
### Phase 2: Integration (post-arch-decision)
1. Integrate startup script into the bootable USB image
2. Wire model selection into the hardware inventory module (T5)
3. Add API config template for fallback provider
### Phase 3: Polish
1. Benchmark model quality on real BSOD dumps
2. Tune prompt templates for diagnostic tasks
3. Optimize context window usage (shorter contexts = faster on local)
4. Consider Knowledge Distillation: fine-tune a smaller model on repair-centric data
---
## 10. Risk Register
| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Target PC has < 8 GB RAM | Medium | High (7B won't load) | Ship 3B fallback model |
| USB 2.0 port (slow loading) | High (old PCs) | Medium (75s load time) | Show progress indicator, use --no-mmap |
| Qwen2.5 underperforms on diagnostics | Low | Medium | Test on real dumps, fall back to API |
| Boot env is WinPE (no llama.cpp) | TBD from T1 | Critical | Pivot to API-only or switch boot env |
| Network available but slow | Medium | Low | Local handles all diagnostics; API only for complex |
| Model licensing changes | Low | Medium | Apache 2.0 is permissive and stable |
---
## Appendix A: Quick-Reference Commands
```bash
# Start local LLM server
llama-server \
-m /usb/hermes/models/qwen2.5-7b-instruct-q4_k_m.gguf \
--host 127.0.0.1 \
--port 8080 \
--ctx-size 4096 \
--n-gpu-layers 0 \
--no-mmap \
--flash-attn \
--threads $(nproc)
# Verify it's running
curl http://localhost:8080/v1/models
# Test inference
curl http://localhost:8080/v1/completions \
-H "Content-Type: application/json" \
-d '{
"prompt": "What does BSOD 0x0000001A (MEMORY_MANAGEMENT) indicate?",
"model": "qwen2.5-7b-instruct-q4_k_m",
"max_tokens": 200,
"temperature": 0.1
}'
# Hermes provider config (for rescue profile)
# ~/.hermes/profiles/rescue/config.yaml:
# model:
# base_url: http://localhost:8080/v1
# api_key: ""
# default: qwen2.5-7b-instruct-q4_k_m
```
## Appendix B: Automatic Model Selection Script (pseudocode)
```
1. Check target PC RAM: free -g | grep Mem
2. If total RAM >= 8 GB → use 7B Q4_K_M (~7 GB needed)
3. If total RAM >= 4 GB → use 3B Q4_K_M (~4 GB needed)
4. If total RAM < 4 GB → fallback mode: API-only IF network, else limited scripted diagnostics
5. Launch llama-server with selected model
6. Wait for healthy endpoint
7. Launch Hermes agent with local provider config
```
+56
View File
@@ -0,0 +1,56 @@
# Hermes Portable Rescue — Source & Build
This directory contains the build scripts and source files for the Hermes Portable Rescue USB runtime.
## Files
| File | Purpose |
|------|---------|
| `build-hermes-runtime.sh` | **Main build script** — creates the portable Hermes runtime package |
| `bootstrap-venv.sh` | Standalone venv bootstrap (can run on target machine) |
| `autorun.inf` | Windows USB autorun configuration |
| `hermes/config.yaml` | Hermes Agent configuration for rescue mode |
| `hermes/launch.sh` | Linux launch script |
| `hermes/launch.ps1` | Windows PowerShell launch script |
| `hermes/tool-manifest.json` | Tool registry for the agent |
| `hermes/scripts/check-env.sh` | Environment verification script |
| `hermes/scripts/mount-storage.sh` | Storage partition mounting helper |
| `hermes/README.md` | Runtime package documentation (for the USB) |
## Building
```bash
# From the project root:
./src/build-hermes-runtime.sh
# Or with custom output path:
./src/build-hermes-runtime.sh --output /mnt/usb/hermes
```
The build script:
1. Creates the directory structure
2. Copies configuration files from `src/hermes/`
3. Creates a Python 3.11 virtual environment with Hermes Agent
4. Installs dependencies (pyyaml, psutil, requests)
5. Prunes the venv to reduce size
6. Collects diagnostic tool binaries
7. Generates the tool manifest and launch scripts
## Output
After building, `src/hermes/` contains the complete portable runtime:
```
hermes/
├── venv/ ← Python 3.11 + Hermes Agent (60-80 MB)
├── config.yaml ← Agent configuration
├── launch.sh ← Linux entry point
├── launch.ps1 ← Windows entry point
├── tool-manifest.json ← Tool registry
├── tools/ ← Diagnostic binaries
├── scripts/ ← Helper scripts
├── pip-freeze.txt ← Installed package manifest
└── README.md ← Runtime docs
```
Copy the entire `hermes/` directory to your USB drive and run `./launch.sh`.
+3
View File
@@ -0,0 +1,3 @@
"""
Hermes Portable Rescue — diagnostic tool package.
"""
+10
View File
@@ -0,0 +1,10 @@
[Autorun]
Action=Start Hermes Portable Rescue
Label=Hermes Rescue USB
Icon=hermes\hermes.ico
Open=hermes\launch.ps1
UseAutoPlay=1
[Autorun.Deprecated]
shell\hermes\command=hermes\launch.ps1
shell\hermes=Start Hermes Rescue
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# =============================================================================
# Hermes Portable Rescue — Bootstrap Python Virtual Environment
# =============================================================================
# Creates and populates the portable Python 3.11 venv with Hermes Agent.
# Designed to run either:
# - On the build machine (before USB deployment)
# - On the target machine (as part of first-run setup)
#
# Usage:
# ./bootstrap-venv.sh [--target DIR] [--python PYTHON_BIN] [--no-prune]
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_DIR="${1:-$SCRIPT_DIR/hermes}"
PYTHON_BIN="${2:-python3}"
PRUNE=true
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--target) TARGET_DIR="$2"; shift 2 ;;
--python) PYTHON_BIN="$2"; shift 2 ;;
--no-prune) PRUNE=false; shift ;;
*) break ;;
esac
done
VENV_DIR="$TARGET_DIR/venv"
REQUIREMENTS_FILE="$TARGET_DIR/requirements.txt"
# Colors
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
# --- Preflight ---------------------------------------------------------------
info "Hermes Portable Rescue — Venv Bootstrap"
info "Target: $TARGET_DIR"
info "Python: $PYTHON_BIN"
command -v "$PYTHON_BIN" >/dev/null 2>&1 || error "Python not found: $PYTHON_BIN"
PYVER=$("$PYTHON_BIN" --version 2>&1)
info "Detected: $PYVER"
if [[ ! "$PYVER" =~ Python\ 3\.(1[1-9]|2[0-9]|30) ]]; then
error "Python 3.11+ required (got: $PYVER)"
fi
# --- Create venv -------------------------------------------------------------
if [[ -f "$VENV_DIR/bin/python3" ]]; then
info "Venv already exists — recreating"
rm -rf "$VENV_DIR"
fi
info "Creating virtual environment…"
"$PYTHON_BIN" -m venv "$VENV_DIR" --clear
info "Upgrading pip, setuptools, wheel…"
"$VENV_DIR/bin/pip3" install --upgrade pip setuptools wheel 2>&1 | tail -1
# --- Install Hermes Agent ----------------------------------------------------
info "Installing Hermes Agent and dependencies…"
"$VENV_DIR/bin/pip3" install --no-cache-dir \
hermes-agent \
pyyaml \
psutil \
requests \
2>&1 | tail -3
# Record installed packages
"$VENV_DIR/bin/pip3" freeze > "$TARGET_DIR/pip-freeze.txt"
info "Installed packages recorded to pip-freeze.txt ($(wc -l < "$TARGET_DIR/pip-freeze.txt") packages)"
# --- Prune (optional) --------------------------------------------------------
if [[ "$PRUNE" = true ]]; then
info "Pruning venv to reduce size…"
REMOVED=0
# Remove __pycache__ dirs
while IFS= read -r d; do
rm -rf "$d" && ((REMOVED++))
done < <(find "$VENV_DIR" -type d -name '__pycache__' 2>/dev/null)
# Remove .pyc files
find "$VENV_DIR" -type f -name '*.pyc' -delete 2>/dev/null
# Remove test directories
for dir in tests test testing; do
while IFS= read -r d; do
rm -rf "$d" && ((REMOVED++))
done < <(find "$VENV_DIR" -type d -name "$dir" 2>/dev/null)
done
# Remove dist-info metadata (keep only latest versions)
find "$VENV_DIR" -type d -name '*.dist-info' -exec rm -rf {} + 2>/dev/null || true
# Remove share, man, doc
rm -rf "$VENV_DIR/share" 2>/dev/null || true
info "Removed $REMOVED directories during prune"
fi
# --- Report ------------------------------------------------------------------
VENV_SIZE=$(du -sh "$VENV_DIR" | cut -f1)
TOTAL_SIZE=$(du -sh "$TARGET_DIR" | cut -f1)
echo ""
echo "============================================"
echo " Bootstrap Complete"
echo "============================================"
echo " Venv size: $VENV_SIZE"
echo " Total target: $TOTAL_SIZE"
echo " Python: $("$VENV_DIR/bin/python3" --version 2>&1)"
echo " Pip packages: $(wc -l < "$TARGET_DIR/pip-freeze.txt")"
echo "============================================"
echo ""
info "Next: copy $TARGET_DIR to USB and run launch.sh"
Regular → Executable
+21
View File
@@ -97,6 +97,27 @@ find "$VENV_DIR" -type d -name 'tests' -exec rm -rf {} + 2>/dev/null || true
find "$VENV_DIR" -type f -name '*.pyc' -delete 2>/dev/null || true
rm -rf "$VENV_DIR/share" 2>/dev/null || true
# --- Step 3b: Create agent/hermes entry point ---------------------------------
# The architecture doc and usb-image.sh expect HERMES_DIR/agent/hermes.
# After venv build, symlink to the installed hermes CLI:
# agent/hermes → ../venv/bin/hermes
# Before venv build, a wrapper script (src/hermes/agent/hermes) provides
# a helpful message directing the user to build.
info "Creating agent entry point…"
mkdir -p "$OUTPUT_DIR/agent"
# Create the executable entry point for autorun.sh compatibility
if [[ -f "$VENV_DIR/bin/hermes" ]]; then
# After build: symlink to the venv binary
ln -sf "../venv/bin/hermes" "$OUTPUT_DIR/agent/hermes"
info " agent/hermes → venv/bin/hermes (symlink)"
elif [[ -f "$SCRIPT_DIR/hermes/agent/hermes" ]]; then
# Copy the static wrapper from source (pre-build state)
cp "$SCRIPT_DIR/hermes/agent/hermes" "$OUTPUT_DIR/agent/hermes"
chmod +x "$OUTPUT_DIR/agent/hermes"
info " agent/hermes = static wrapper (pre-build)"
fi
# --- Step 4: Collect portable diagnostic tools -------------------------------
if [[ -d "$TOOL_CACHE" ]]; then
info "Copying prebuilt diagnostic tools from $TOOL_CACHE"
+58
View File
@@ -0,0 +1,58 @@
# Hermes Portable Rescue — Runtime Package
This directory contains the portable Hermes Agent runtime.
It is designed to be copied onto a USB drive and booted on any Windows PC.
## Contents
| Path | Purpose |
|------|---------|
| `venv/` | Portable Python 3.11 virtual environment with Hermes Agent |
| `config.yaml` | Hermes Agent configuration for rescue mode |
| `launch.sh` | Linux / Linux-live boot entry point |
| `launch.ps1` | Windows / WinPE entry point |
| `tools/` | Portable diagnostic binaries (smartctl, stress-ng, etc.) |
| `scripts/` | Diagnostic helper scripts |
| `models/` | (optional) Local LLM model files |
| `tool-manifest.json` | Tool registry for Hermes Agent capabilities |
| `pip-freeze.txt` | Installed Python packages manifest |
## Quick Start
**Linux:**
```bash
cd /path/to/hermes
./launch.sh
```
**Windows (PowerShell):**
```powershell
powershell -ExecutionPolicy Bypass -File .\launch.ps1
```
## Setup Order
1. Run `bootstrap-venv.sh` or `build-hermes-runtime.sh` to create the venv
2. (Optional) Put a 4-bit quantized GGUF model in `models/` for local LLM inference
3. Copy the entire `hermes/` directory to a USB drive
4. Boot the target machine from a Linux live USB
5. Mount the Hermes USB drive
6. Run `./launch.sh`
## Size Budget
| Component | Approx Size |
|-----------|-------------|
| Python venv (pruned) | 60-80 MB |
| Hermes Agent + deps | 20-30 MB |
| Tools (smartctl, stress-ng) | 10-15 MB |
| Models (optional) | 2-5 GB |
| **Total (minimal)** | **~100 MB** |
## Notes
- The venv is created for the **host architecture** (x86_64).
- For ARM64 targets, build on an ARM machine or use QEMU user-mode.
- LLM model files go in `models/` if running local inference.
- Network access is optional — API-based LLM fallback works with internet.
- All diagnostic reports are saved to the same USB in `reports/` directory.
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# =============================================================================
# Hermes Portable Rescue — Agent Entry Point
# =============================================================================
# This is the main entry point that usb-image.sh and autorun.sh expect at
# HERMES_DIR/agent/hermes. It finds and runs the Hermes CLI from the portable
# venv, or prompts to build if the venv doesn't exist yet.
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HERMES_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Try the venv binary first (post-build)
if [[ -x "$HERMES_ROOT/venv/bin/hermes" ]]; then
exec "$HERMES_ROOT/venv/bin/hermes" "$@"
fi
# Try installed system hermes (pre-build / development)
if command -v hermes &>/dev/null; then
exec hermes "$@"
fi
# Fallback: no hermes found
echo "╔══════════════════════════════════════════════════════╗" >&2
echo "║ Hermes Agent not found. ║" >&2
echo "║ Run the build script to create the portable venv: ║" >&2
echo "║ ║" >&2
echo "║ ./src/build-hermes-runtime.sh ║" >&2
echo "║ ║" >&2
echo "║ Or install hermes-agent via pip: ║" >&2
echo "║ pip install hermes-agent ║" >&2
echo "╚══════════════════════════════════════════════════════╝" >&2
exit 1
+58 -87
View File
@@ -1,94 +1,65 @@
# Hermes Portable Rescue — Agent Configuration
# =============================================
# This is the **source** config for the portable Hermes agent.
# It is copied into the USB image by build/usb-image.sh.
#
# LLM strategy determined by research:llm-strat task (t_9ba3b456).
# Boot environment determined by research:boot-env task (t_163b7bb2).
# This file is deployed onto the USB and read by Hermes Agent at startup.
# Generated by src/build-hermes-runtime.sh
agent:
name: "Hermes Portable Rescue"
name: "hermes-portable-rescue"
version: "1.0.0"
mode: "rescue"
description: "AI-driven Windows PC diagnostic & repair toolkit"
author: "OPLabs / Shawn"
repo: "https://gitea.ourpad.casa/shawn/hermes-portable-rescue"
llm:
# Strategy: hybrid (local for offline, API fallback when network available)
strategy: hybrid
local_model: "/hermes/models/qwen2.5-7b-q4.gguf"
local_model_url: "https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF/resolve/main/qwen2.5-7b-instruct-q4_K_M.gguf"
api_fallback: true
api_provider: openai
api_endpoint: "https://api.openai.com/v1"
model: "gpt-4o-mini"
temperature: 0.2
max_tokens: 2048
diagnostics:
auto_run: true
timeout_seconds: 3600
log_level: info
save_reports: true
report_path: "/hermes/reports/"
output_format: markdown
modules:
hardware_inventory:
enabled: true
script: "/hermes/scripts/hardware.py"
description: "CPU, RAM, GPU, disk enumeration and health checks"
bsod_analysis:
enabled: true
script: "/hermes/scripts/bsod.py"
description: "Windows minidump parser with LLM interpretation"
scan_paths:
- "C:/Windows/Minidump/"
- "C:/Windows/memory.dmp"
- "/mnt/c/Windows/Minidump/"
driver_check:
enabled: true
script: "/hermes/scripts/drivers.py"
description: "Hardware-aware driver scanner and updater"
stress_test:
enabled: false # Manual activation only — can destabilise a failing system
script: "/hermes/scripts/stress.py"
description: "RAM, CPU, and GPU stress testing orchestration"
confirm_before_run: true
backup_restore:
enabled: false # Manual activation only
script: "/hermes/scripts/backup.py"
description: "Disk imaging and file-level backup/restore"
confirm_before_run: true
network:
dhcp: true
fallback_static: false
dns:
- "8.8.8.8"
- "1.1.1.1"
auto_connect_wifi: false
wifi_ssid: ""
wifi_password: ""
filesystem:
mount_windows: true
mount_point: "/mnt/c"
ntfs_driver: "ntfs-3g"
fallback_fuse: true
display:
mode: interactive # interactive | silent | report-only
color: true
refresh_interval: 5 # seconds for live diagnostic dashboard
mode: "rescue" # rescue mode: diagnostic-first, no persistent chat
logging:
file: "/hermes/reports/session.log"
level: info # debug | info | warn | error
level: "INFO"
file: "/tmp/hermes-rescue.log"
max_size_mb: 10
rotation: 3
backup_count: 3
# Provider: uses local LLM if available, otherwise API fallback
llm:
mode: "auto" # auto = prefer local, fallback to api
local:
model_path: "${HERMES_ROOT}/models/q4_0.gguf"
context_size: 4096
threads: 4
api:
provider: "openrouter"
model: "openai/gpt-4o-mini"
timeout_seconds: 30
health_check:
max_retries: 2
fallback_to_local: true
rescue:
# Diagnostic sequence — ordered by dependency
diagnostic_sequence:
- hardware_inventory # gather system specs first
- disk_health # SMART checks
- memory_test # RAM stress
- bsod_analyzer # minidump parsing
- cpu_gpu_stress # thermal/load tests
- driver_check # driver inventory
# Storage paths on the USB
paths:
tools: "${HERMES_ROOT}/tools"
scripts: "${HERMES_ROOT}/scripts"
models: "${HERMES_ROOT}/models"
diagnostics: "${HERMES_ROOT}/diagnostics"
reports: "/tmp/hermes-reports"
# Auto-proceed — run the diagnostic sequence on boot without user input
auto_diagnose: true
# Report output
report:
format: "markdown"
output_dir: "/tmp/hermes-reports"
filename_template: "diagnostic-report-{timestamp}.md"
tools:
manifest: "${HERMES_ROOT}/tool-manifest.json"
timeout_seconds: 300
network:
# Optional: API-based LLM fallback needs network
probe_on_start: true
timeout_seconds: 10
Regular → Executable
View File
Regular → Executable
+10 -1
View File
@@ -60,4 +60,13 @@ echo ""
echo "[*] Starting Hermes Agent in rescue mode…"
echo ""
exec "$HERMES_ROOT/venv/bin/hermes" --config "$HERMES_CONFIG"
# Try agent/hermes first (autorun.sh compatible), fall back to venv binary
if [[ -x "$HERMES_ROOT/agent/hermes" ]]; then
exec "$HERMES_ROOT/agent/hermes" --config "$HERMES_CONFIG"
elif [[ -f "$HERMES_ROOT/venv/bin/hermes" ]]; then
exec "$HERMES_ROOT/venv/bin/hermes" --config "$HERMES_CONFIG"
else
echo "[✗] Hermes Agent not found at agent/hermes or venv/bin/hermes"
echo " Run src/build-hermes-runtime.sh first."
exit 1
fi
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# =============================================================================
# Hermes Portable Rescue — Environment Check Script
# =============================================================================
# Verifies the runtime environment has everything needed.
# Run: ./scripts/check-env.sh
# =============================================================================
set -euo pipefail
HERMES_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PASS=0
FAIL=0
WARN=0
info() { echo -e " [INFO] $*"; }
pass() { echo -e " [PASS] $*"; ((PASS++)); }
fail() { echo -e " [FAIL] $*"; ((FAIL++)); }
warn() { echo -e " [WARN] $*"; ((WARN++)); }
echo "============================================"
echo " Hermes Environment Check"
echo "============================================"
echo " Root: $HERMES_ROOT"
echo ""
# --- Python ------------------------------------------------------------------
echo "--- Python ---"
if [[ -f "$HERMES_ROOT/venv/bin/python3" ]]; then
PYVER=$("$HERMES_ROOT/venv/bin/python3" --version 2>&1)
pass "Python venv: $PYVER"
else
fail "Python venv not found at $HERMES_ROOT/venv/bin/python3"
fi
if [[ -f "$HERMES_ROOT/pip-freeze.txt" ]]; then
PKG_COUNT=$(wc -l < "$HERMES_ROOT/pip-freeze.txt")
pass "Package manifest: $PKG_COUNT packages"
else
fail "pip-freeze.txt not found"
fi
# --- Hermes Agent ------------------------------------------------------------
echo ""
echo "--- Hermes Agent ---"
if [[ -f "$HERMES_ROOT/venv/bin/hermes" ]]; then
pass "Hermes binary found"
else
fail "Hermes binary not found"
fi
if [[ -f "$HERMES_ROOT/config.yaml" ]]; then
pass "Config: config.yaml"
else
fail "config.yaml missing"
fi
# --- Tools -------------------------------------------------------------------
echo ""
echo "--- Diagnostic Tools ---"
if [[ -f "$HERMES_ROOT/tool-manifest.json" ]]; then
pass "Tool manifest: tool-manifest.json"
# Check each tool in the manifest
for tool in smartctl stress-ng dmidecode lshw memtester; do
if command -v "$tool" &>/dev/null; then
pass "Tool available: $tool"
else
warn "Tool not found: $tool (may need install)"
fi
done
else
fail "Tool manifest missing"
fi
# --- Storage -----------------------------------------------------------------
echo ""
echo "--- Storage ---"
ROOT_FREE=$(df -m "$HERMES_ROOT" | tail -1 | awk '{print $4}')
pass "Free space on Hermes root: ${ROOT_FREE}MB"
if [[ -d "/mnt/hermes-data" ]]; then
MNT_FREE=$(df -m /mnt/hermes-data | tail -1 | awk '{print $4}')
pass "Data mount: /mnt/hermes-data (${MNT_FREE}MB free)"
else
warn "Data mount /mnt/hermes-data not found (reports go to /tmp)"
fi
TMP_FREE=$(df -m /tmp | tail -1 | awk '{print $4}')
pass "Temp space: ${TMP_FREE}MB in /tmp"
# --- Network -----------------------------------------------------------------
echo ""
echo "--- Network ---"
if command -v curl &>/dev/null; then
if curl -s --connect-timeout 3 -o /dev/null -w "%{http_code}" https://google.com 2>/dev/null | grep -qE '^[23]'; then
pass "Internet: connected"
else
warn "Internet: not reachable (offline mode)"
fi
else
warn "curl not available"
fi
# --- Summary ----------------------------------------------------------------
echo ""
echo "============================================"
echo " Results: $PASS passed, $FAIL failed, $WARN warnings"
echo "============================================"
if [[ $FAIL -gt 0 ]]; then
echo ""
echo "Fix failures before running the agent."
exit 1
fi
exit 0
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# =============================================================================
# Hermes Portable Rescue — Storage Mount Helper
# =============================================================================
# Mounts NTFS/BTRFS/ext4 partitions for diagnostic access.
# Usage: ./scripts/mount-storage.sh [--all | --probe]
# =============================================================================
set -euo pipefail
MOUNT_BASE="/mnt/hermes-data"
PROBE_ONLY=false
while [[ $# -gt 0 ]]; do
case "$1" in
--probe) PROBE_ONLY=true; shift ;;
--all) PROBE_ONLY=false; shift ;;
*) echo "Usage: $0 [--all | --probe]"; exit 1 ;;
esac
done
mkdir -p "$MOUNT_BASE"
echo "[hermes:mount] Probing storage devices…"
# List all block devices
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL 2>/dev/null | grep -v loop | head -20
if [[ "$PROBE_ONLY" = true ]]; then
echo ""
echo "[hermes:mount] Probe complete — use --all to mount"
exit 0
fi
# Mount all unmounted NTFS, ext4, btrfs partitions (excluding live system)
echo ""
echo "[hermes:mount] Mounting accessible partitions…"
echo ""
MOUNT_COUNT=0
while IFS= read -r line; do
DEVICE=$(echo "$line" | awk '{print $1}')
FSTYPE=$(echo "$line" | awk '{print $2}')
MOUNTPOINT=$(echo "$line" | awk '{print $3}')
LABEL=$(echo "$line" | awk '{print $4}')
# Skip: already mounted, swap, live system root
if [[ "$MOUNTPOINT" != "(not mounted)" ]]; then continue; fi
if [[ "$FSTYPE" = "swap" ]]; then continue; fi
if [[ "$DEVICE" =~ (sr|loop|ram) ]]; then continue; fi
MOUNT_PATH="$MOUNT_BASE/$DEVICE"
mkdir -p "$MOUNT_PATH"
case "$FSTYPE" in
ntfs)
if command -v ntfs-3g &>/dev/null; then
ntfs-3g -o ro,noatime "/dev/$DEVICE" "$MOUNT_PATH" 2>/dev/null && \
echo " [✓] /dev/$DEVICE (NTFS${LABEL:+: $LABEL}) → $MOUNT_PATH" && \
((MOUNT_COUNT++)) || \
echo " [✗] /dev/$DEVICE mount failed"
else
echo " [ ] /dev/$DEVICE (NTFS — ntfs-3g not available)"
fi
;;
ext4|ext3|ext2|xfs|btrfs)
mount -o ro,noatime "/dev/$DEVICE" "$MOUNT_PATH" 2>/dev/null && \
echo " [✓] /dev/$DEVICE (${FSTYPE}${LABEL:+: $LABEL}) → $MOUNT_PATH" && \
((MOUNT_COUNT++)) || \
echo " [✗] /dev/$DEVICE mount failed"
;;
vfat|exfat)
mount -o ro,noatime,uid=1000 "/dev/$DEVICE" "$MOUNT_PATH" 2>/dev/null && \
echo " [✓] /dev/$DEVICE (${FSTYPE}${LABEL:+: $LABEL}) → $MOUNT_PATH" && \
((MOUNT_COUNT++)) || \
echo " [✗] /dev/$DEVICE mount failed"
;;
*)
echo " [ ] /dev/$DEVICE ($FSTYPE — unsupported)"
;;
esac
done < <(lsblk -nlo NAME,FSTYPE,MOUNTPOINT,LABEL 2>/dev/null)
echo ""
echo "[hermes:mount] $MOUNT_COUNT partitions mounted under $MOUNT_BASE"
echo "[hermes:mount] Use 'umount $MOUNT_BASE/*' when done"