Perseus Deployment Guide
July 11, 2026 · View on GitHub
"The mirror lets Perseus face the monster clearly, without meeting her gaze."
This guide walks through deploying every Perseus surface — context engine, Bastra Recall (Mnēmē memory), Pythia oracle, Agora task board, Synthesis, and Prefetch cache warming — on a Hermes Agent host. By the end, you will have a self-maintaining deployment where every component is watchdogged, health-checked, and wired into Hermes cron.
Audience: anyone running Hermes Agent who wants the full Perseus ecosystem running autonomously on their server.
Assumed environment: Linux (Unraid/Docker), Hermes Agent installed, Node.js 22+ available, Python 3.10+ available.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ Hermes Agent │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Context │ │ Pythia │ │ Agora │ │ Synthesis │ │
│ │ Engine │ │ Suggest │ │ Reporter │ │ Digest │ │
│ │ (5m cron) │ │ (8am) │ │ (9am) │ │ (Mon 9am) │ │
│ └────┬──────┘ └────┬─────┘ └────┬─────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ (each renders a prompt the host agent answers with its │
│ own model — Perseus runs no inference of its own) │
│ │ │
│ ┌────────────────────────┼─────────────────────────────┐ │
│ │ Bastra Recall (Mnēmē — :6723) │ │
│ │ MCP server → 9 tools → memory vault on disk │ │
│ │ Daemon + watchdog (5m) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Prefetch │ │Checkpoint│ │Auto- │ │
│ │ Cache │ │ (3am) │ │update │ │
│ │ (30m) │ │ │ │ (4am) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
Design principle: every component has a watchdog or is self-recovering. If something dies, it comes back without human intervention. If it stays dead, the watchdog cron reports it.
Prerequisites
Before starting, verify you have:
| Dependency | How to check | Minimum version |
|---|---|---|
| Hermes Agent | hermes --version | v0.14.0+ |
| Node.js | node --version | v22+ |
| Python 3 | python3 --version | 3.10+ |
pyyaml | python3 -c "import yaml" | any |
| ANTHROPIC_API_KEY | grep ANTHROPIC_API_KEY ~/.hermes/.env | valid key |
Key files and paths (adjust if your Hermes home differs):
| Path | Purpose |
|---|---|
~/.hermes/config.yaml | Main Hermes config — MCP servers, plugins, cron |
~/.hermes/.env | API keys and secrets |
~/.hermes/scripts/ | Cron scripts live here |
~/.hermes/logs/ | All Perseus component logs |
~/.hermes/bastra-vault/ | Bastra memory vault on disk |
/workspace/perseus/perseus.py | Standalone Perseus artifact |
Step 1: Bastra Recall — The Mnēmē Memory Backend
Bastra Recall is Perseus's persistent memory layer. It replaces the legacy
flat-file Mnēmē subsystem. The daemon serves a REST API at :6723; Hermes
connects via MCP to expose 9 memory tools (mcp_bastra_recall_*).
1.1 Verify the Node.js Binary
ls ~/.nvm/versions/node/v22.22.3/bin/node
# Should print the path. If missing, install Node ≥22 via nvm.
1.2 Create the Daemon Script
Save as ~/.hermes/scripts/bastra-daemon.sh:
#!/usr/bin/env bash
# bastra-daemon.sh — start/stop/status for the bastra-recall persistent daemon
set -euo pipefail
VAULT_PATH="${BASTRA_VAULT_PATH:-$HOME/.hermes/bastra-vault}"
DAEMON_JS="/workspace/bastra-recall/packages/daemon/dist/index.js"
NODE_BIN="$HOME/.nvm/versions/node/v22.22.3/bin/node"
LOG_DIR="$HOME/.hermes/logs"
PID_FILE="/tmp/bastra-daemon.pid"
PORT="${BASTRA_HTTP_PORT:-6723}"
mkdir -p "$LOG_DIR" "$VAULT_PATH"
cmd="${1:-status}"
case "$cmd" in
start)
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "bastra-daemon: already running (pid $(cat "$PID_FILE"))"
exit 0
fi
nohup env BASTRA_VAULT_PATH="$VAULT_PATH" "$NODE_BIN" "$DAEMON_JS" \
> "$LOG_DIR/bastra-daemon.log" 2>&1 &
echo $! > "$PID_FILE"
for i in $(seq 1 10); do
if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
echo "bastra-daemon: started (pid $(cat "$PID_FILE"))"
exit 0
fi
sleep 1
done
echo "bastra-daemon: started but health check timed out (pid $(cat "$PID_FILE"))"
exit 1
;;
stop)
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
kill "$pid" 2>/dev/null || true
rm -f "$PID_FILE"
echo "bastra-daemon: stopped"
else
echo "bastra-daemon: not running"
fi
;;
status)
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "bastra-daemon: running (pid $(cat "$PID_FILE"))"
curl -sf "http://127.0.0.1:$PORT/health" 2>/dev/null && echo " (healthy)" || echo " (unhealthy)"
else
echo "bastra-daemon: not running"
rm -f "$PID_FILE"
exit 1
fi
;;
*)
echo "usage: \$0 {start|stop|status}"
exit 1
;;
esac
chmod +x ~/.hermes/scripts/bastra-daemon.sh
1.3 Create the Daemon Watchdog
Save as ~/.hermes/scripts/bastra-daemon-watchdog.sh:
#!/usr/bin/env bash
# Bastra daemon watchdog — restarts daemon if health check fails.
# Runs via Hermes cron (no-agent mode, every 5m).
set -euo pipefail
DAEMON_SCRIPT="$HOME/.hermes/scripts/bastra-daemon.sh"
PORT=6723
if curl -sf "http://127.0.0.1:$PORT/health" >/dev/null 2>&1; then
exit 0 # healthy — silent
fi
# Daemon is down or unhealthy — restart it
bash "$DAEMON_SCRIPT" stop 2>/dev/null || true
bash "$DAEMON_SCRIPT" start
chmod +x ~/.hermes/scripts/bastra-daemon-watchdog.sh
1.4 Configure MCP in Hermes
Add to ~/.hermes/config.yaml under mcp_servers::
mcp_servers:
bastra-recall:
command: /home/hermeswebui/.hermes/home/.nvm/versions/node/v22.22.3/bin/node
args:
- /workspace/bastra-recall/packages/daemon/dist/mcp-forwarder.js
env:
BASTRA_VAULT_PATH: /home/hermeswebui/.hermes/bastra-vault
BASTRA_FORWARDER_SPAWN: '0'
timeout: 120
Important: The command must be an absolute path to your Node.js binary. The
$HOME in cron execution may differ from your interactive shell — use the full
absolute path. Verify with which node or ls ~/.nvm/versions/node/*/bin/node.
1.5 Verify Bastra
# Start the daemon
bash ~/.hermes/scripts/bastra-daemon.sh start
# Expected: bastra-daemon: started (pid NNNN)
# Health check
curl -s http://127.0.0.1:6723/health
# Expected: {"ok":true,"vault_size":N,"version":"0.1.0"}
# Check MCP tools are registered
hermes mcp list | grep bastra
# Expected: bastra-recall ... ✓ enabled
1.6 Schedule the Watchdog
hermes cron create "every 5m" \
--name "Bastra-recall daemon watchdog" \
--script bastra-daemon-watchdog.sh \
--no-agent \
--deliver local
Step 2: Perseus LLM Proxy (deprecated — no longer required)
Deprecated. Perseus runs no inference of its own (observe model): Pythia, Synthesis, and Mnēmē now render prompts for the host agent to answer with the model it already uses, and no component calls a provider directly. This proxy is no longer needed for a Perseus deployment — skip this step. The section is retained only for operators with an existing proxy still wired into unrelated tooling.
The LLM proxy is a thin Python HTTP server that forwards OpenAI-compatible
/v1/chat/completions requests to the Anthropic API, injecting your API key.
2.1 Create the Proxy Script
Save as ~/.hermes/scripts/perseus-llm-proxy.py:
#!/usr/bin/env python3
"""Perseus LLM Proxy — thin OpenAI-compat forwarder to Anthropic."""
import http.server
import json
import os
import urllib.request
import urllib.error
PORT = 18080
ANTHROPIC_URL = "https://api.anthropic.com/v1/chat/completions"
ANTHROPIC_VERSION = "2023-06-01"
class ProxyHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
print(f"[perseus-llm-proxy] {fmt % args}")
def do_GET(self):
if self.path in ("/health", "/v1/models"):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"status": "ok"}).encode())
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
if "/chat/completions" not in self.path:
self.send_response(404)
self.end_headers()
return
api_key = os.environ.get("ANTHROPIC_API_KEY", "")
if not api_key:
self.send_response(500)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": "ANTHROPIC_API_KEY not set"}).encode())
return
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
req = urllib.request.Request(
ANTHROPIC_URL,
data=body,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
"x-api-key": api_key,
"anthropic-version": ANTHROPIC_VERSION,
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=120) as resp:
resp_body = resp.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(resp_body)
except urllib.error.HTTPError as e:
err_body = e.read()
self.send_response(e.code)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(err_body)
except Exception as exc:
self.send_response(502)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": str(exc)}).encode())
def _load_env_file(path: str) -> None:
try:
with open(os.path.expanduser(path)) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, val = line.partition("=")
key = key.strip()
val = val.strip().strip('"').strip("'")
if key and key not in os.environ:
os.environ[key] = val
except FileNotFoundError:
pass
if __name__ == "__main__":
_load_env_file("~/.hermes/.env")
api_key = os.environ.get("ANTHROPIC_API_KEY", "")
if not api_key:
print("[perseus-llm-proxy] WARNING: ANTHROPIC_API_KEY not found")
else:
print(f"[perseus-llm-proxy] API key loaded (...{api_key[-4:]})")
server = http.server.HTTPServer(("127.0.0.1", PORT), ProxyHandler)
print(f"[perseus-llm-proxy] Listening on 127.0.0.1:{PORT} → Anthropic API")
server.serve_forever()
2.2 Start the Proxy
# Start as a background process via Hermes
# (Hermes will track it; use background=true in terminal tool)
python3 ~/.hermes/scripts/perseus-llm-proxy.py &
# Or start directly:
nohup python3 ~/.hermes/scripts/perseus-llm-proxy.py \
> ~/.hermes/logs/perseus-llm-proxy.log 2>&1 &
2.3 Create the Proxy Watchdog
Save as ~/.hermes/scripts/perseus-llm-proxy-watchdog.sh:
#!/usr/bin/env bash
# Perseus LLM proxy watchdog — restarts proxy if health check fails.
set -euo pipefail
PORT=18080
PROXY_SCRIPT="$HOME/.hermes/scripts/perseus-llm-proxy.py"
LOG="$HOME/.hermes/logs/perseus-llm-proxy.log"
if curl -sf "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
exit 0
fi
echo "[$(date)] Perseus LLM proxy down — restarting"
nohup python3 "${PROXY_SCRIPT}" >> "${LOG}" 2>&1 &
echo "[$(date)] Restarted with PID $!"
chmod +x ~/.hermes/scripts/perseus-llm-proxy-watchdog.sh
2.4 Schedule the Proxy Watchdog
hermes cron create "every 10m" \
--name "Perseus LLM proxy watchdog" \
--script perseus-llm-proxy-watchdog.sh \
--no-agent \
--deliver local
2.5 Verify the Proxy
curl -s http://127.0.0.1:18080/health
# Expected: {"status": "ok"}
Step 3: Perseus Context Engine
The context engine renders workspace state into .hermes.md / AGENTS.md files so
Hermes has live context at session start.
This requires the perseus-context-engine skill to be installed in Hermes.
3.1 Schedule Context Refresh
hermes cron create "every 5m" \
--name "Perseus: refresh workspace context" \
--skills perseus-context-engine \
--workdir /workspace/perseus \
--deliver local
What it does: Every 5 minutes, Hermes loads the perseus-context-engine skill,
runs it in the Perseus workspace, and updates the context files.
Step 4: Remaining Cron Jobs
4.1 Checkpoint — Daily at 3 AM
Saves a recovery waypoint so session context survives crashes.
Save as ~/.hermes/scripts/perseus-checkpoint-cron.sh:
#!/usr/bin/env bash
set -euo pipefail
PERSEUS_PY="/workspace/perseus/perseus.py"
if [[ -f "${PERSEUS_PY}" ]]; then
PERSEUS_CMD=(python3 "${PERSEUS_PY}")
else
echo "[$(date)] FATAL: cannot find /workspace/perseus/perseus.py" >&2
exit 2
fi
# Load env
if [[ -f ~/.hermes/.env ]]; then
set -a; source ~/.hermes/.env; set +a
fi
TIMESTAMP=$(date +%Y-%m-%dT%H%M)
TASK="auto-checkpoint: daily cron at ${TIMESTAMP}"
echo "[$(date)] Running perseus checkpoint: ${TASK}"
"${PERSEUS_CMD[@]}" checkpoint \
--task "${TASK}" \
--status "scheduled" \
--notes "Automated daily checkpoint via Hermes cron (0 3 * * *)"
echo "[$(date)] Checkpoint saved OK"
chmod +x ~/.hermes/scripts/perseus-checkpoint-cron.sh
hermes cron create "0 3 * * *" \
--name "Perseus daily checkpoint" \
--script perseus-checkpoint-cron.sh \
--no-agent \
--deliver local
4.2 Auto-Update — Daily at 4 AM
Checks for and applies Perseus updates from git.
Save as ~/.hermes/scripts/perseus-auto-update.sh:
#!/usr/bin/env bash
set -euo pipefail
PERSEUS_PY="/workspace/perseus/perseus.py"
if [[ -f "${PERSEUS_PY}" ]]; then
PERSEUS_CMD=(python3 "${PERSEUS_PY}")
else
echo "[$(date)] FATAL: cannot find /workspace/perseus/perseus.py" >&2
exit 2
fi
CHECK_OUTPUT=$("${PERSEUS_CMD[@]}" update 2>&1) || true
if echo "${CHECK_OUTPUT}" | grep -q "up to date"; then
exit 0
fi
echo "[$(date)] Updates available, applying …"
if "${PERSEUS_CMD[@]}" update --apply 2>&1; then
echo "[$(date)] Updated successfully"
else
echo "[$(date)] Update FAILED — check perseus update --apply manually" >&2
exit 1
fi
chmod +x ~/.hermes/scripts/perseus-auto-update.sh
hermes cron create "0 4 * * *" \
--name "Perseus auto-update" \
--script perseus-auto-update.sh \
--no-agent \
--deliver local
4.3 Prefetch Cache Warmer — Every 30 Minutes
Pre-warms directive caches for all workspace context files, keeping renders fast.
Save as ~/.hermes/scripts/perseus-prefetch-warmer.sh:
#!/usr/bin/env bash
set -euo pipefail
PERSEUS_PY="/workspace/perseus/perseus.py"
ERRORS=0
WARMED=0
while IFS= read -r -d '' f; do
WORKSPACE_DIR="$(dirname "$(dirname "$f")")"
if [[ "$f" == */AGENTS.md ]]; then
WORKSPACE_DIR="$(dirname "$f")"
fi
if python3 "${PERSEUS_PY}" prefetch --workspace "${WORKSPACE_DIR}" "$f" >/dev/null 2>&1; then
WARMED=$((WARMED + 1))
else
ERRORS=$((ERRORS + 1))
fi
done < <(find /workspace -maxdepth 4 \( -path '*/.perseus/context.md' -o -name 'AGENTS.md' \) -print0 2>/dev/null)
if [ $ERRORS -gt 0 ]; then
echo "[$(date)] Prefetch: ${WARMED}/$((WARMED + ERRORS)) ok, ${ERRORS} errors"
exit 1
fi
exit 0
chmod +x ~/.hermes/scripts/perseus-prefetch-warmer.sh
hermes cron create "every 30m" \
--name "Perseus prefetch cache warmer" \
--script perseus-prefetch-warmer.sh \
--no-agent \
--deliver local
4.4 Agora Task Board Reporter — Daily at 9 AM
Summarizes the Perseus task board (open / in-progress / completed tasks).
Save as ~/.hermes/scripts/perseus-agora-reporter.sh:
#!/usr/bin/env bash
set -euo pipefail
PERSEUS_PY="/workspace/perseus/perseus.py"
OUTPUT=$(cd /workspace/perseus && python3 "${PERSEUS_PY}" agora list 2>&1)
OPEN=$(echo "$OUTPUT" | awk '/^OPEN$/{found=1; next} /^IN_PROGRESS$/{found=0} found && /^task-/{count++} END{print count+0}')
IN_PROGRESS=$(echo "$OUTPUT" | awk '/^IN_PROGRESS$/{found=1; next} /^COMPLETED$/{found=0} found && /^task-/{count++} END{print count+0}')
if [ "$OPEN" -eq 0 ] && [ "$IN_PROGRESS" -eq 0 ]; then
exit 0
fi
echo "Agora Task Board"
echo "================"
echo "Open: $OPEN | In Progress: $IN_PROGRESS"
echo ""
echo "$OUTPUT" | awk '/^OPEN$/,/^BLOCKED$/' | head -30
chmod +x ~/.hermes/scripts/perseus-agora-reporter.sh
hermes cron create "0 9 * * *" \
--name "Perseus Agora status reporter" \
--script perseus-agora-reporter.sh \
--no-agent \
--deliver local
4.5 Pythia Suggest — Daily at 8 AM
Runs the Pythia tool oracle on the highest-priority open task, using the LLM proxy.
Save as ~/.hermes/scripts/perseus-pythia-suggest.sh:
#!/usr/bin/env bash
set -euo pipefail
PERSEUS_PY="/workspace/perseus/perseus.py"
LLM_URL="${PYTHIA_LLM_URL:-http://127.0.0.1:18080}"
LLM_MODEL="${PYTHIA_LLM_MODEL:-claude-sonnet-4-6}"
TASK_LINE=$(cd /workspace/perseus && python3 "${PERSEUS_PY}" agora list 2>&1 | awk '/^OPEN$/{found=1; next} /^IN_PROGRESS$/{found=0} found && /^task-/{print; exit}')
if [ -z "$TASK_LINE" ]; then
exit 0
fi
TASK_ID=$(echo "$TASK_LINE" | awk '{print \$1}')
echo "Pythia: analyzing ${TASK_ID}..."
echo ""
cd /workspace/perseus && python3 "${PERSEUS_PY}" suggest \
--llm openai-compat \
--model-url "${LLM_URL}" \
--model "${LLM_MODEL}" \
"${TASK_ID}: $(echo "$TASK_LINE" | cut -d' ' -f3-)" 2>&1
chmod +x ~/.hermes/scripts/perseus-pythia-suggest.sh
hermes cron create "0 8 * * *" \
--name "Perseus Pythia suggest" \
--script perseus-pythia-suggest.sh \
--no-agent \
--deliver local
4.6 Synthesis Weekly Digest — Monday at 9 AM
Generates a cited summary of project changes from CHANGELOG.md and ROADMAP.md.
Save as ~/.hermes/scripts/perseus-synthesis-digest.sh:
#!/usr/bin/env bash
set -euo pipefail
PERSEUS_PY="/workspace/perseus/perseus.py"
LLM_URL="${SYNTHESIS_LLM_URL:-http://127.0.0.1:18080}"
LLM_MODEL="${SYNTHESIS_LLM_MODEL:-claude-sonnet-4-6}"
cd /workspace/perseus
python3 "${PERSEUS_PY}" synthesize \
--source CHANGELOG.md \
--source ROADMAP.md \
--llm openai-compat \
--model-url "${LLM_URL}" \
--model "${LLM_MODEL}" \
--enable-generation \
"What changed this week and what's coming next?" 2>&1
chmod +x ~/.hermes/scripts/perseus-synthesis-digest.sh
hermes cron create "0 9 * * 1" \
--name "Perseus Synthesis weekly digest" \
--script perseus-synthesis-digest.sh \
--no-agent \
--deliver local
Step 5: Verification Checklist
Run through these checks after deployment. All should pass.
5.1 Core Services
# Bastra daemon
curl -s http://127.0.0.1:6723/health
# Expected: {"ok":true,"vault_size":N,"version":"0.1.0"}
# LLM proxy
curl -s http://127.0.0.1:18080/health
# Expected: {"status":"ok"}
# Perseus CLI
cd /workspace/perseus && python3 perseus.py --version
# Expected: perseus v1.0.N
5.2 MCP Memory Tools
hermes mcp list | grep bastra
# Expected: bastra-recall ... ✓ enabled
# In a Hermes session, these tools should appear:
# mcp_bastra_recall_recall, mcp_bastra_recall_load_memory,
# mcp_bastra_recall_save_memory, mcp_bastra_recall_find_document,
# mcp_bastra_recall_read_document, mcp_bastra_recall_save_document,
# mcp_bastra_recall_recategorize_document, mcp_bastra_recall_move_document,
# mcp_bastra_recall_open_document
5.3 Cron Jobs
hermes cron list | grep -E 'Perseus|Bastra'
All of these should show [active]:
| Job name | Schedule | Mode |
|---|---|---|
| Perseus: refresh workspace context | every 5m | agent |
| Perseus daily checkpoint | 0 3 * * * | no-agent |
| Perseus auto-update | 0 4 * * * | no-agent |
| Perseus prefetch cache warmer | every 30m | no-agent |
| Perseus LLM proxy watchdog | every 10m | no-agent |
| Bastra-recall daemon watchdog | every 5m | no-agent |
| Perseus Agora status reporter | 0 9 * * * | no-agent |
| Perseus Pythia suggest | 0 8 * * * | no-agent |
| Perseus Synthesis weekly digest | 0 9 * * 1 | no-agent |
5.4 On-Demand Commands
# Pythia (requires LLM proxy running)
cd /workspace/perseus
python3 perseus.py suggest --llm openai-compat \
--model-url http://127.0.0.1:18080 \
--model claude-sonnet-4-6 \
"How should I add a new directive to Perseus?"
# Agora task board
python3 perseus.py agora list
# Prefetch a single file
python3 perseus.py prefetch .perseus/context.md
# Graph a context file
python3 perseus.py graph .perseus/context.md
5.5 Bastra Memory Round-Trip
From a Hermes session, test the full memory pipeline:
# Save a test memory
> mcp_bastra_recall_save_memory with title="test-deployment", type="project-fact",
summary="Deployment verification test", body="This is a test.",
topic_path=["test"], tags=["test"], scope="test", recall_when=["testing deployment"]
# Recall it
> mcp_bastra_recall_recall with query="deployment verification"
Step 6: Troubleshooting
Bastra daemon won't start
# Check the log
tail -20 ~/.hermes/logs/bastra-daemon.log
# Common causes:
# 1. Node binary path wrong — verify with: ls ~/.nvm/versions/node/*/bin/node
# 2. Daemon JS missing — verify: ls /workspace/bastra-recall/packages/daemon/dist/index.js
# 3. Port in use — check: curl -s http://127.0.0.1:6723/health
LLM proxy returns empty responses
The proxy forwards to Anthropic. Check:
# Is the API key valid?
grep ANTHROPIC_API_KEY ~/.hermes/.env
# Test Anthropic directly
curl -s https://api.anthropic.com/v1/chat/completions \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-4-6","max_tokens":10,"messages":[{"role":"user","content":"hello"}]}'
Perseus CLI not found in cron
The cron execution environment may have a stripped PATH. Always use the standalone
perseus.py artifact (python3 /workspace/perseus/perseus.py) in cron scripts,
never the pip-installed binary. The standalone artifact has zero import dependencies
beyond pyyaml.
Cron scripts fail silently
No-agent crons with deliver: local only deliver stdout. If the script has no output,
you won't see failures unless you check the cron run log:
hermes cron list | grep <job-name>
# Look for "error" in the last run status
Pythia/Synthesis time out
These use the LLM proxy which calls Anthropic. The default 30s timeout in Perseus may be too short. Increase it:
# Via environment variable (checked by perseus.py's run_llm)
export PERSEUS_LLM_TIMEOUT=120
# Or in ~/.perseus/config.yaml:
# llm:
# timeout_s: 120
MCP tools not appearing
After adding bastra-recall to mcp_servers: in config.yaml, you need a fresh
Hermes session:
/new
Or restart the Hermes process. MCP servers connect at session start.
Cron Job Reference Card
TIME │ JOB
────────┼──────────────────────────────────────────
:00 │ Context engine refresh (every 5m)
:05 │ Bastra daemon watchdog (every 5m)
:10 │ LLM proxy watchdog (every 10m)
:30 │ Prefetch cache warmer (every 30m)
03:00 │ Daily checkpoint
04:00 │ Auto-update
08:00 │ Pythia suggest
09:00 │ Agora status reporter
09:00 │ Synthesis weekly digest (Monday only)
See Also
- Hermes Integration — Perseus ↔ Hermes LLM routing
- Container Deployment — Docker / compose deployment
- Setup & Configuration Guide — Full config reference, automation, security
- spec/components.md — Component architecture
- spec/integration.md — Adapter patterns
- Quickstart — First-time setup