hermes-labyrinth-observability
Read-only observability dashboard plugin for Hermes Agent — journeys, crossings, guideposts, and reports.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o hermes-labyrinth-observability.zip https://jpskill.com/download/22970.zip && unzip -o hermes-labyrinth-observability.zip && rm hermes-labyrinth-observability.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/22970.zip -OutFile "$d\hermes-labyrinth-observability.zip"; Expand-Archive "$d\hermes-labyrinth-observability.zip" -DestinationPath $d -Force; ri "$d\hermes-labyrinth-observability.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
hermes-labyrinth-observability.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
hermes-labyrinth-observabilityフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Claude が読む原文 SKILL.md(中身を展開)
この本文は AI(Claude)が読むための原文(英語または中国語)です。日本語訳は順次追加中。
Hermes Labyrinth
Skill by ara.so — Daily 2026 Skills collection.
Hermes Labyrinth is a read-only observability dashboard plugin for Hermes Agent. It turns autonomous agent runs into a navigable map of crossings (prompts, tool calls, tool results, failures, model switches, subagents, approvals, memory hits, redactions, context compression, cron runs) with exportable evidence. It is not a chat UI — it is a black-box recorder for agents moving through unknown work.
Install
Plugin Directory Install
mkdir -p ~/.hermes/plugins
git clone https://github.com/stainlu/hermes-labyrinth.git ~/.hermes/plugins/hermes-labyrinth
Start or restart the Hermes dashboard:
hermes dashboard
If the dashboard is already running, rescan plugins without restarting:
curl http://127.0.0.1:9119/api/dashboard/plugins/rescan
Open the dashboard in your browser and select the Labyrinth tab.
Optional Theme
mkdir -p ~/.hermes/dashboard-themes
cp ~/.hermes/plugins/hermes-labyrinth/theme/hermes-labyrinth.yaml ~/.hermes/dashboard-themes/
What Labyrinth Tracks
| View | Contents |
|---|---|
| Journey index | Recent CLI, dashboard, gateway, cron, and delegated work |
| Labyrinth map | Ordered crossings through a selected agent journey |
| Inspector | Input, output, duration, status, evidence, guideposts per crossing |
| Guideposts | Generated observations backed by local evidence |
| Skill atlas | Bundled, optional, external, and user skill inventory |
| Cron gate | Scheduled autonomy, next runs, last failures, workdirs |
| Model ferry | Model/provider transitions across sessions |
| Reports | Redacted Markdown and JSON exports for one journey |
API Surface
All endpoints are read-only. The plugin API is served by Hermes dashboard at:
http://127.0.0.1:9119/api/plugins/hermes-labyrinth/
Endpoints
GET /api/plugins/hermes-labyrinth/health
GET /api/plugins/hermes-labyrinth/journeys
GET /api/plugins/hermes-labyrinth/journeys/{journey_id}
GET /api/plugins/hermes-labyrinth/journeys/{journey_id}/crossings
GET /api/plugins/hermes-labyrinth/skills
GET /api/plugins/hermes-labyrinth/cron
GET /api/plugins/hermes-labyrinth/guideposts
GET /api/plugins/hermes-labyrinth/reports/{journey_id}.json
GET /api/plugins/hermes-labyrinth/reports/{journey_id}.md
Example: Fetch All Journeys
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys | jq .
Example: Fetch Crossings for a Journey
JOURNEY_ID="your-journey-id"
curl "http://127.0.0.1:9119/api/plugins/hermes-labyrinth/journeys/${JOURNEY_ID}/crossings" | jq .
Example: Export a Journey Report as Markdown
JOURNEY_ID="your-journey-id"
curl "http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/${JOURNEY_ID}.md" > report.md
Example: Export a Journey Report as JSON
JOURNEY_ID="your-journey-id"
curl "http://127.0.0.1:9119/api/plugins/hermes-labyrinth/reports/${JOURNEY_ID}.json" > report.json
Example: Health Check
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/health
Python API Client Examples
The plugin backend lives at dashboard/plugin_api.py. You can also call the HTTP API from any language. Here are Python examples:
import urllib.request
import json
BASE = "http://127.0.0.1:9119/api/plugins/hermes-labyrinth"
def get_journeys():
with urllib.request.urlopen(f"{BASE}/journeys") as r:
return json.loads(r.read())
def get_crossings(journey_id: str):
with urllib.request.urlopen(f"{BASE}/journeys/{journey_id}/crossings") as r:
return json.loads(r.read())
def get_report_json(journey_id: str):
with urllib.request.urlopen(f"{BASE}/reports/{journey_id}.json") as r:
return json.loads(r.read())
def get_report_md(journey_id: str) -> str:
with urllib.request.urlopen(f"{BASE}/reports/{journey_id}.md") as r:
return r.read().decode("utf-8")
# Usage
journeys = get_journeys()
for j in journeys:
print(j["id"], j.get("status"), j.get("started_at"))
Iterate Crossings and Inspect Tool Calls
import urllib.request
import json
BASE = "http://127.0.0.1:9119/api/plugins/hermes-labyrinth"
def inspect_tool_crossings(journey_id: str):
with urllib.request.urlopen(f"{BASE}/journeys/{journey_id}/crossings") as r:
crossings = json.loads(r.read())
for crossing in crossings:
if crossing.get("type") == "tool_call":
print(f"Tool: {crossing['tool']}")
print(f" Status: {crossing.get('status')}")
print(f" Duration: {crossing.get('duration_ms')}ms")
print(f" Input: {json.dumps(crossing.get('input', {}))[:200]}")
print()
inspect_tool_crossings("your-journey-id")
Download and Save All Reports for Recent Journeys
import urllib.request
import json
import pathlib
BASE = "http://127.0.0.1:9119/api/plugins/hermes-labyrinth"
OUT = pathlib.Path("./labyrinth-reports")
OUT.mkdir(exist_ok=True)
with urllib.request.urlopen(f"{BASE}/journeys") as r:
journeys = json.loads(r.read())
for j in journeys[:10]: # last 10 journeys
jid = j["id"]
try:
with urllib.request.urlopen(f"{BASE}/reports/{jid}.json") as r:
(OUT / f"{jid}.json").write_bytes(r.read())
with urllib.request.urlopen(f"{BASE}/reports/{jid}.md") as r:
(OUT / f"{jid}.md").write_bytes(r.read())
print(f"Saved reports for {jid}")
except Exception as e:
print(f"Failed {jid}: {e}")
JavaScript / Frontend API Examples
The frontend plugin bundle lives in dashboard/dist/. If you're extending the UI or writing a custom integration:
const BASE = "http://127.0.0.1:9119/api/plugins/hermes-labyrinth";
async function fetchJourneys() {
const res = await fetch(`${BASE}/journeys`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
async function fetchCrossings(journeyId) {
const res = await fetch(`${BASE}/journeys/${journeyId}/crossings`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
async function fetchReportMarkdown(journeyId) {
const res = await fetch(`${BASE}/reports/${journeyId}.md`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
}
// Example: log all failed crossings in the most recent journey
async function logFailures() {
const journeys = await fetchJourneys();
if (!journeys.length) return;
const crossings = await fetchCrossings(journeys[0].id);
const failed = crossings.filter(c => c.status === "failure" || c.status === "error");
console.table(failed.map(c => ({
type: c.type,
tool: c.tool ?? "-",
duration_ms: c.duration_ms,
error: c.error?.slice(0, 120),
})));
}
logFailures();
Build & Development
The frontend is built from src/parts/*.js + src/labyrinth.css into dashboard/dist/. The demo index.html is generated with content-hash query strings.
# Build dashboard/dist and index.html
npm run build
# Run reproducibility and parse checks
npm run check
# Run browser smoke tests (headless Chrome)
npm run smoke
# Smoke-test the deployed GitHub Pages demo
npm run smoke:live
# Run all tests (build checks, fixture tests, smoke)
npm test
Full Test Suite
npm test
Runs:
- Reproducible build checks for
dashboard/distandindex.html - Frontend JavaScript parse checks
- Backend Python parse checks
- API normalization fixture tests (including numeric Hermes timestamps)
- Packed-artifact and dead-control regressions
- Headless Chrome smoke coverage for map modes, route changes, search, dataset switching, and threshold filter
Repository Layout
.
├── dashboard/
│ ├── manifest.json # Hermes dashboard plugin manifest
│ ├── plugin_api.py # Read-only API over local Hermes state
│ └── dist/ # Generated dashboard plugin bundle
├── docs/
│ ├── CONCEPT.md
│ ├── DESIGN_BRIEF.md
│ └── FUNCTIONAL_SPEC.md
├── scripts/
│ ├── build-plugin.mjs # Builds dashboard/dist and index.html
│ ├── smoke-demo.mjs # Browser smoke test for public demo
│ ├── test-plugin-api.py # Fixture tests for API normalization
│ └── verify.mjs # Local verification checks
├── src/
│ ├── demo/ # GitHub Pages demo source
│ ├── parts/ # Ordered frontend source chunks
│ └── labyrinth.css # Frontend CSS source
├── theme/
│ └── hermes-labyrinth.yaml
├── index.html # Generated GitHub Pages demo
└── package.json
Architecture
Hermes local state
├─ state.db sessions/messages
├─ skills directories
└─ cron config
↓
dashboard/plugin_api.py
↓
/api/plugins/hermes-labyrinth/*
↓
src/parts/*.js + src/labyrinth.css
↓ npm run build
dashboard/dist/*
↓
Hermes dashboard tab: Labyrinth
Data Policy (Important)
- Read-only by design: does not start, stop, resume, mutate, or create Hermes sessions.
- Secret redaction: applied to previews and reports.
- Unknown fields: stay unknown — not silently dropped.
- Reports: generated from local Hermes state only.
- Public demo: uses sample/mocked data — not live telemetry.
Common Patterns
Check Plugin Health Before Querying
import urllib.request, json
def is_labyrinth_healthy() -> bool:
try:
with urllib.request.urlopen(
"http://127.0.0.1:9119/api/plugins/hermes-labyrinth/health",
timeout=3
) as r:
data = json.loads(r.read())
return data.get("status") == "ok"
except Exception:
return False
if not is_labyrinth_healthy():
print("Labyrinth plugin not reachable — is `hermes dashboard` running?")
Filter Journeys by Type
import urllib.request, json
BASE = "http://127.0.0.1:9119/api/plugins/hermes-labyrinth"
with urllib.request.urlopen(f"{BASE}/journeys") as r:
journeys = json.loads(r.read())
# Filter to only cron-triggered journeys
cron_journeys = [j for j in journeys if j.get("origin") == "cron"]
# Filter to only failed journeys
failed_journeys = [j for j in journeys if j.get("status") in ("failure", "error")]
Summarize Crossing Types in a Journey
from collections import Counter
import urllib.request, json
BASE = "http://127.0.0.1:9119/api/plugins/hermes-labyrinth"
def summarize_journey(journey_id: str):
with urllib.request.urlopen(f"{BASE}/journeys/{journey_id}/crossings") as r:
crossings = json.loads(r.read())
counts = Counter(c.get("type", "unknown") for c in crossings)
total_ms = sum(c.get("duration_ms", 0) for c in crossings)
print(f"Journey {journey_id}: {len(crossings)} crossings, {total_ms}ms total")
for ctype, n in counts.most_common():
print(f" {ctype}: {n}")
Troubleshooting
Plugin tab not appearing in dashboard
- Confirm clone location:
ls ~/.hermes/plugins/hermes-labyrinth/dashboard/manifest.json - Restart
hermes dashboardor rescan:curl http://127.0.0.1:9119/api/dashboard/plugins/rescan - Check
manifest.jsonis valid JSON.
API returns 404
- Verify the plugin loaded:
curl http://127.0.0.1:9119/api/plugins/hermes-labyrinth/health - Confirm
hermes dashboardis running on port9119.
Build artifacts out of date
npm run build
# Then verify reproducibility
npm run check
Smoke tests fail locally
- Requires headless Chrome/Chromium available in
PATH. - Run
npm run smokefor local,npm run smoke:livefor the deployed Pages demo.
Reports contain redacted fields
This is expected. Labyrinth applies secret redaction to all previews and exports by design. Raw values are only accessible inside the Hermes process itself.
Numeric timestamps in API responses
The API normalization layer handles numeric Hermes timestamps automatically (covered by fixture tests in scripts/test-plugin-api.py). If you consume the API directly, expect either ISO 8601 strings or Unix epoch integers in timestamp fields.
Links
- Live demo: https://stainlu.github.io/hermes-labyrinth/
- Hermes Agent: https://github.com/NousResearch/hermes-agent
- Release v0.1.0: https://github.com/stainlu/hermes-labyrinth/releases/tag/v0.1.0
- License: MIT