| name | hermes-labyrinth-observability |
| description | Hermes Labyrinth observability plugin for monitoring autonomous agent journeys, crossings, and execution traces |
| triggers | ["install hermes labyrinth plugin","view agent journey traces","inspect hermes agent crossings","monitor autonomous agent execution","analyze hermes agent behavior","export agent journey reports","debug hermes agent failures","track agent tool calls"] |
Hermes Labyrinth Observability Plugin
Skill by ara.so — Hermes Skills collection.
Hermes Labyrinth is a read-only observability plugin for Hermes Agent that provides detailed monitoring of autonomous agent execution. It visualizes agent "journeys" as sequences of "crossings" (prompts, tool calls, results, failures, model switches, subagents) and generates exportable reports with built-in secret redaction.
Installation
Install into the Hermes user plugin directory:
mkdir -p ~/.hermes/plugins
git clone https://github.com/stainlu/hermes-labyrinth.git ~/.hermes/plugins/hermes-labyrinth
After installation, restart the Hermes dashboard:
hermes dashboard
The Labyrinth tab will appear in the dashboard UI at http://127.0.0.1:9119.
Docker Installation
For Docker-based Hermes installations, mount the plugin directory:
mkdir -p ~/.hermes/plugins
git clone https://github.com/stainlu/hermes-labyrinth.git ~/.hermes/plugins/hermes-labyrinth
cd ~/.hermes/plugins/hermes-labyrinth
git checkout v0.1.3
Mount the Hermes home directory into your container and restart the dashboard.
Plugin Management
Rescan Plugins (Frontend Only)
Refresh discovered frontend manifests without full restart:
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan
Note: Backend API changes require a full dashboard restart.
Disable/Remove Plugin
hermes plugins disable hermes-labyrinth
rm -rf ~/.hermes/plugins/hermes-labyrinth
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan
API Reference
All endpoints are read-only and include automatic secret redaction.
Health Check
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/health
Returns plugin status and Hermes state accessibility.
List Journeys
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys
Returns recent CLI, dashboard, gateway, cron, and delegated agent sessions.
Get Journey Details
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys/{journey_id}
Returns metadata for a specific journey including start time, status, and model usage.
Get Journey Crossings
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys/{journey_id}/crossings
Returns ordered sequence of crossings (messages, tool calls, results) for a journey.
Get Skills Inventory
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/skills
Returns:
skills: effective skills loaded
shadowed: expected user-over-bundled overrides
duplicates: true duplicate diagnostics
errors: skill scan errors
Get Cron Configuration
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/cron
Returns scheduled autonomy jobs, next run times, last failures, and working directories.
Get Guideposts
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/guideposts
Returns generated observations backed by local evidence across journeys.
Export Reports
JSON format:
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/{journey_id}.json > journey.json
Markdown format:
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/{journey_id}.md > journey.md
Both formats include automatic secret redaction.
Python Plugin API Extension
Hermes Labyrinth extends the dashboard with custom backend routes. The core implementation is in dashboard/plugin_api.py.
Example: Custom API Route
from fastapi import APIRouter, HTTPException
from typing import Dict, Any
import os
router = APIRouter(prefix="/api/plugins/hermes-labyrinth")
@router.get("/custom-endpoint")
async def custom_endpoint() -> Dict[str, Any]:
"""Example custom endpoint for plugin."""
hermes_home = os.environ.get("HERMES_HOME", os.path.expanduser("~/.hermes"))
state_db = os.path.join(hermes_home, "state.db")
if not os.path.exists(state_db):
raise HTTPException(status_code=503, detail="Hermes state unavailable")
return {
"status": "ok",
"hermes_home": hermes_home
}
Accessing Hermes State
The plugin reads from:
~/.hermes/state.db: Sessions and messages (SQLite)
~/.hermes/skills/: User and bundled skills
~/.hermes/cron/: Scheduled job configurations
import sqlite3
import os
def query_journeys(hermes_home: str):
"""Query recent sessions from Hermes state.db"""
state_db = os.path.join(hermes_home, "state.db")
conn = sqlite3.connect(state_db)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("""
SELECT session_id, created_at, updated_at, status, model
FROM sessions
ORDER BY updated_at DESC
LIMIT 100
""")
sessions = [dict(row) for row in cursor.fetchall()]
conn.close()
return sessions
Frontend Development
The dashboard UI is built from modular JavaScript components.
Build Process
npm run build
Generates:
dashboard/dist/labyrinth-bundle.js: Combined JS from src/parts/*.js
dashboard/dist/labyrinth.css: Copied from src/labyrinth.css
index.html: GitHub Pages demo with content hashes
Development Workflow
npm run build
npm run check
npm run smoke
npm run smoke:live
Frontend Structure
const LabyrinthState = {
journeys: [],
selectedJourney: null,
crossings: [],
mode: 'chronological'
};
async function fetchJourneys() {
const response = await fetch('/api/plugins/hermes-labyrinth/journeys');
return response.json();
}
function renderJourneyCard(journey) {
return `
<div class="journey-card" data-id="${journey.id}">
<h3>${escapeHtml(journey.title)}</h3>
<time>${new Date(journey.created_at * 1000).toLocaleString()}</time>
</div>
`;
}
Configuration
Optional Theme
mkdir -p ~/.hermes/dashboard-themes
cp ~/.hermes/plugins/hermes-labyrinth/theme/hermes-labyrinth.yaml ~/.hermes/dashboard-themes/
Plugin Manifest
Located at dashboard/manifest.json:
{
"name": "hermes-labyrinth",
"version": "0.1.3",
"title": "Labyrinth",
"description": "Agent journey observability",
"entry_js": "dist/labyrinth-bundle.js",
"entry_css": "dist/labyrinth.css",
"api_module": "plugin_api"
}
Common Patterns
Filtering Journeys by Status
curl -s http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys | \
jq '.journeys[] | select(.status == "completed")'
Analyzing Tool Call Patterns
curl -s http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys/{journey_id}/crossings | \
jq '.crossings[] | select(.type == "tool_call") | .tool_name' | \
sort | uniq -c
Exporting Multiple Journeys
curl -s http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys | \
jq -r '.journeys[].id' | head -10 | while read id; do
curl -s "http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/$id.json" > "journey_$id.json"
done
Monitoring Agent Failures
curl -s http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys | \
jq '.journeys[] | select(.status == "failed") | {id, error, updated_at}'
Security and Redaction
Labyrinth applies Hermes core secret redaction to all outputs:
from hermes.redactor import redact_secrets
def safe_export(content: str) -> str:
"""Apply redaction before export."""
try:
return redact_secrets(content)
except Exception:
return "[redaction unavailable]"
Verification Before Production
Test redaction with dummy secrets:
hermes chat "Test: sk-dummy-key-12345"
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/{journey_id}.json | grep "sk-dummy"
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/{journey_id}.md | grep "sk-dummy"
Testing
Run Full Test Suite
npm test
Includes:
- Build reproducibility checks
- JavaScript/Python parse validation
- API normalization fixture tests
- Headless Chrome smoke tests
Backend API Tests
python3 scripts/test-plugin-api.py
Tests timestamp normalization, redaction, and data structure handling.
Manual Smoke Test
npm run smoke
Runs headless Chrome against built demo:
- Map mode switching
- Route navigation
- Search functionality
- Dataset switching
- Threshold filtering
Troubleshooting
Plugin Not Appearing in Dashboard
- Verify installation path:
ls -la ~/.hermes/plugins/hermes-labyrinth/dashboard/manifest.json
- Restart dashboard (rescan doesn't mount new Python routes):
hermes dashboard stop
hermes dashboard
- Check dashboard logs for plugin API import errors.
Backend/API Diagnostic in UI
This means the Python API routes failed to load:
- Restart dashboard completely (not just rescan)
- Check Python imports:
python3 -c "import sys; sys.path.insert(0, '$HOME/.hermes/plugins/hermes-labyrinth/dashboard'); import plugin_api"
- Verify Hermes home exists:
ls -la ~/.hermes/state.db
Redaction Unavailable Error
If reports show [redaction unavailable]:
- Verify Hermes core is installed properly
- Check redactor import:
from hermes.redactor import redact_secrets
- This is a fail-closed safety mechanism — plugin won't export unredacted content
Empty Journeys List
- Verify agent sessions exist:
sqlite3 ~/.hermes/state.db "SELECT COUNT(*) FROM sessions;"
- Check file permissions:
ls -la ~/.hermes/state.db
- Run health check:
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/health
Build Failures
rm -rf dashboard/dist index.html
npm run build
npm run check
Development References