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:

DependencyHow to checkMinimum version
Hermes Agenthermes --versionv0.14.0+
Node.jsnode --versionv22+
Python 3python3 --version3.10+
pyyamlpython3 -c "import yaml"any
ANTHROPIC_API_KEYgrep ANTHROPIC_API_KEY ~/.hermes/.envvalid key

Key files and paths (adjust if your Hermes home differs):

PathPurpose
~/.hermes/config.yamlMain Hermes config — MCP servers, plugins, cron
~/.hermes/.envAPI 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.pyStandalone 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 nameScheduleMode
Perseus: refresh workspace contextevery 5magent
Perseus daily checkpoint0 3 * * *no-agent
Perseus auto-update0 4 * * *no-agent
Perseus prefetch cache warmerevery 30mno-agent
Perseus LLM proxy watchdogevery 10mno-agent
Bastra-recall daemon watchdogevery 5mno-agent
Perseus Agora status reporter0 9 * * *no-agent
Perseus Pythia suggest0 8 * * *no-agent
Perseus Synthesis weekly digest0 9 * * 1no-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