Loading...
Loading...
Expert in deploying and using Hermes HUD Web UI for monitoring AI agent memory, sessions, costs, and health
npx skill4agent add aradotso/hermes-skills hermes-hudui-consciousness-monitorSkill by ara.so — Hermes Skills collection.
~/.hermes/~/.hermes/git clone https://github.com/joeynyc/hermes-hudui.git
cd hermes-hudui
./install.sh
hermes-hudui# Clone repository
git clone https://github.com/joeynyc/hermes-hudui.git
cd hermes-hudui
# Create virtual environment
python3.11 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install Python dependencies
pip install -e .
# Install Node dependencies and build frontend
cd frontend
npm install
npm run build
cd ..
# Launch
hermes-huduicd hermes-hudui
source venv/bin/activate && hermes-hudui# In zsh (quotes required)
pip install 'hermes-hudui[tui]'
# In bash/fish (quotes optional but safe)
pip install 'hermes-hudui[tui]'# Custom port (default: 3001)
export HERMES_HUD_PORT=8080
# Custom Hermes data directory (default: ~/.hermes/)
export HERMES_DATA_DIR=/path/to/custom/hermes/data
# WebSocket host (default: localhost)
export HERMES_WS_HOST=0.0.0.0~/.hermes/~/.hermes/
├── identity.json # Agent identity and configuration
├── memory/ # Persistent memory store
├── sessions/ # Session logs and history
├── skills/ # Installed skills
├── cron/ # Scheduled jobs
├── projects/ # Project tracking
├── health/ # Health metrics
└── costs/ # Usage and cost tracking~/.hermes-hud/replays/
├── session_abc123/
│ ├── replay.redacted.json
│ ├── replay.md
│ ├── replay.html
│ ├── share-card.png
│ └── fork.json# Start the web UI
hermes-hudui
# Start on custom port
HERMES_HUD_PORT=8080 hermes-hudui
# Point to custom Hermes data directory
HERMES_DATA_DIR=/custom/path hermes-hudui| Key | Action |
|---|---|
| Switch between tabs 1-10 |
| Open theme picker |
| Open command palette |
thermes-hudui~/.hermes/replay.redacted.jsonreplay.mdreplay.htmlshare-card.pngfork.jsonassets/example-replay.redacted.json# server/handlers/custom_handler.py
from server.websocket import send_message
async def handle_custom_event(websocket, data):
"""
Custom event handler for new dashboard features.
"""
event_type = data.get("type")
if event_type == "request_custom_metric":
metric_data = compute_custom_metric()
await send_message(websocket, {
"type": "custom_metric",
"payload": metric_data
})
def compute_custom_metric():
# Read from ~/.hermes/ or compute live data
return {
"metric_name": "agent_velocity",
"value": 42,
"timestamp": "2026-05-16T18:00:00Z"
}server/websocket.pyfrom server.handlers.custom_handler import handle_custom_event
async def handle_message(websocket, message):
data = json.loads(message)
if data["type"] == "request_custom_metric":
await handle_custom_event(websocket, data)
# ... existing handlersimport json
from pathlib import Path
def load_hermes_identity():
"""Load agent identity from ~/.hermes/identity.json"""
hermes_dir = Path.home() / ".hermes"
identity_path = hermes_dir / "identity.json"
if identity_path.exists():
with open(identity_path, "r") as f:
return json.load(f)
return None
def get_recent_sessions(limit=10):
"""Get most recent session logs"""
sessions_dir = Path.home() / ".hermes" / "sessions"
if not sessions_dir.exists():
return []
session_files = sorted(
sessions_dir.glob("*.json"),
key=lambda p: p.stat().st_mtime,
reverse=True
)
sessions = []
for session_file in session_files[:limit]:
with open(session_file, "r") as f:
sessions.append(json.load(f))
return sessions// frontend/src/components/CustomMetricWidget.jsx
import React, { useEffect, useState } from 'react';
import { useWebSocket } from '../hooks/useWebSocket';
export function CustomMetricWidget() {
const [metricData, setMetricData] = useState(null);
const { sendMessage, onMessage } = useWebSocket();
useEffect(() => {
// Request custom metric from backend
sendMessage({ type: 'request_custom_metric' });
// Listen for response
const unsubscribe = onMessage((data) => {
if (data.type === 'custom_metric') {
setMetricData(data.payload);
}
});
return unsubscribe;
}, []);
if (!metricData) return <div>Loading...</div>;
return (
<div className="metric-widget">
<h3>{metricData.metric_name}</h3>
<div className="metric-value">{metricData.value}</div>
<div className="metric-timestamp">{metricData.timestamp}</div>
</div>
);
}# server/replay/custom_exporter.py
import json
from pathlib import Path
from datetime import datetime
class CustomReplayExporter:
def __init__(self, session_id, replay_data):
self.session_id = session_id
self.replay_data = replay_data
self.output_dir = Path.home() / ".hermes-hud" / "replays" / session_id
self.output_dir.mkdir(parents=True, exist_ok=True)
def export_json(self):
"""Export custom JSON format"""
output_path = self.output_dir / "custom-export.json"
custom_format = {
"version": "1.0",
"session_id": self.session_id,
"exported_at": datetime.utcnow().isoformat(),
"timeline": self.replay_data.get("timeline", []),
"metadata": self.replay_data.get("metadata", {})
}
with open(output_path, "w") as f:
json.dump(custom_format, f, indent=2)
return output_path
def export_csv(self):
"""Export timeline as CSV"""
import csv
output_path = self.output_dir / "timeline.csv"
timeline = self.replay_data.get("timeline", [])
if not timeline:
return None
with open(output_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=timeline[0].keys())
writer.writeheader()
writer.writerows(timeline)
return output_pathfrom pathlib import Path
import json
def check_agent_health():
"""Check Hermes agent health status"""
health_file = Path.home() / ".hermes" / "health" / "status.json"
if not health_file.exists():
return {"status": "unknown", "message": "Health file not found"}
with open(health_file, "r") as f:
health_data = json.load(f)
return {
"status": health_data.get("status"),
"last_check": health_data.get("timestamp"),
"issues": health_data.get("issues", [])
}def get_cost_summary(model_name=None):
"""Get cost analytics, optionally filtered by model"""
costs_dir = Path.home() / ".hermes" / "costs"
if not costs_dir.exists():
return {"total": 0, "by_model": {}}
total_cost = 0
by_model = {}
for cost_file in costs_dir.glob("*.json"):
with open(cost_file, "r") as f:
data = json.load(f)
model = data.get("model")
cost = data.get("cost", 0)
if model_name and model != model_name:
continue
total_cost += cost
by_model[model] = by_model.get(model, 0) + cost
return {
"total": total_cost,
"by_model": by_model
}// frontend/src/hooks/useAgentHealth.js
import { useEffect, useState } from 'react';
import { useWebSocket } from './useWebSocket';
export function useAgentHealth() {
const [health, setHealth] = useState(null);
const { sendMessage, onMessage } = useWebSocket();
useEffect(() => {
// Request initial health status
sendMessage({ type: 'request_health' });
// Subscribe to health updates
const unsubscribe = onMessage((data) => {
if (data.type === 'health_update') {
setHealth(data.payload);
}
});
// Refresh every 30 seconds
const interval = setInterval(() => {
sendMessage({ type: 'request_health' });
}, 30000);
return () => {
unsubscribe();
clearInterval(interval);
};
}, []);
return health;
}# Error: Address already in use
# Solution: Use a different port
HERMES_HUD_PORT=8080 hermes-hudui# Verify backend is running
ps aux | grep hermes-hudui
# Check logs for WebSocket errors
tail -f ~/.hermes-hud/logs/server.log
# Ensure frontend points to correct WebSocket URL
# Check frontend/.env or frontend/src/config.js# Verify Hermes data exists
ls -la ~/.hermes/
# If data is elsewhere, set environment variable
HERMES_DATA_DIR=/path/to/hermes/data hermes-hudui~/.hermes-hud/replays/# Create directory with correct permissions
mkdir -p ~/.hermes-hud/replays
chmod 755 ~/.hermes-hud/replays
# Check disk space
df -h ~./install.shnpm run build# Check Node.js version
node --version # Should be 18+
# Update Node.js (via nvm)
nvm install 18
nvm use 18
# Clean install
cd frontend
rm -rf node_modules package-lock.json
npm install
npm run build# Configure Nous Tool Gateway (if using gateway)
# In Hermes agent config (~/.hermes/config.json):
{
"gateway": {
"enabled": true,
"api_key": "${NOUS_GATEWAY_API_KEY}"
}
}
# Or configure direct API keys
{
"providers": {
"openai": {
"api_key": "${OPENAI_API_KEY}"
},
"anthropic": {
"api_key": "${ANTHROPIC_API_KEY}"
}
}
}
# Restart Hermes agent and HUD