How the memory system and Main Dashboard are configured. Reference for replicating or troubleshooting these setups.
Three layers of memory, each with a specific purpose.
File: ~/.hermes/SOUL.md
Loaded: Every message (no restart needed)
Purpose: Agent identity, language, style, rules
# Identity
You are [Name] — [description].
# Language
ALWAYS use [language].
# Style
- Be direct without being cold
- Prefer substance over filler
# Rules
- Never hallucinate. Always web search to verify.
- Ask approval before modifying system settings.
When to edit: User mentions response style, language preference, or behavioral rules.
File: ~/.hermes/MEMORY.md
Loaded: Injected into every session at start
Purpose: Environment facts, tool quirks, lessons learned
[Company] environment.
[OS, server info].
[Key tool paths and quirks].
[Project conventions].
What goes here: - User preferences and corrections - Environment discoveries (OS, paths, tool behavior) - Stable conventions that reduce future steering
What does NOT go here: - Session progress, temporary TODOs - System-injected info (model name, provider) - Anything stale in a week
File: ~/.hermes/USER.md
Loaded: Injected into every session
Purpose: User's name, timezone, personal preferences
[Name]. [Timezone]. [Language preference].
[Role, interests].
[Personal notes relevant to agent interaction].
File: ~/.hermes/memories/warm.db (SQLite FTS5)
Script: scripts/warm-memory.py
Hook: scripts/warm-memory-hook.py (pre_llm_call)
Structured memory cards with type/label/content. Auto-injected into agent context before each LLM call.
Card types:
- state — Current facts (e.g., "user prefers concise responses")
- event — Something that happened
- knowledge — Reusable insight
- preference — User preference
CLI:
python3 scripts/warm-memory.py add henry state "prefers concise" "User prefers concise responses"
python3 scripts/warm-memory.py list henry --active
python3 scripts/warm-memory.py search henry "language"
python3 scripts/warm-memory.py block henry # Get cards for injection
Hook (auto-inject):
# config.yaml
hooks:
pre_llm_call:
- command: /opt/data/scripts/warm-memory-hook.py
timeout: 5
The hook reads session info from stdin, determines the user, runs warm-memory.py block <user>, and outputs JSON context injected into the LLM call.
A Flask app that serves markdown docs, project context, and sandbox output.
Script: scripts/md-reader.py
Port: 9119
Venv: /opt/data/venv/dashboard/
| Route | Serves |
|---|---|
/ |
Homepage — dashboard, active plans, categories |
/doc/<path> |
Markdown files from wiki or projects |
/sandbox/ |
List all sandbox sessions |
/sandbox/<name> |
Files in a session |
/sandbox/<name>/file/<path> |
Individual file (renders .md as HTML) |
/wiki/ |
Wiki index |
/projects/<name> |
Project context files |
Serves from /opt/data/ — any file in that tree is accessible at its relative path.
For apps that run on separate ports:
# In md-reader.py
@app.route('/myapp/')
def myapp_proxy():
return proxy_request('http://localhost:5000')
@app.route('/myapp/api/<path:path>')
def myapp_api_proxy(path):
return proxy_request(f'http://localhost:5000/api/{path}')
Main Dashboard auto-starts when the gateway boots.
Hook: hooks/dashboard-startup/
hooks/dashboard-startup/
├── HOOK.yaml
└── handler.py
HOOK.yaml:
name: dashboard-startup
description: "Start md-reader dashboard on gateway boot."
events:
- gateway:startup
handler.py:
import asyncio
import subprocess
import urllib.request
PORT = 9119
CHECK_URL = f"http://localhost:{PORT}/"
PYTHON = "/opt/data/venv/dashboard/bin/python"
SCRIPT = "/opt/data/scripts/md-reader.py"
async def handle(event_type: str, context: dict) -> None:
# Check if already running
try:
resp = urllib.request.urlopen(CHECK_URL, timeout=3)
if resp.status == 200:
print("[dashboard] Already running")
return
except Exception:
pass
# Start the server
subprocess.Popen(
[PYTHON, SCRIPT],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
)
await asyncio.sleep(3)
try:
resp = urllib.request.urlopen(CHECK_URL, timeout=5)
print(f"[dashboard] Started: {resp.status}")
except Exception as e:
print(f"[dashboard] FAILED: {e}")
# Create persistent venv (survives container restart)
python3 -m venv /opt/data/venv/dashboard
/opt/data/venv/dashboard/bin/pip install flask markdown markupsafe requests
Important: Use /opt/data/venv/ not ~/.hermes/ — the latter is ephemeral on Docker/Zeabur.
pkill -f "md-reader.py"
/opt/data/venv/dashboard/bin/python /opt/data/scripts/md-reader.py &
Or via gateway hook: kill -TERM 1 (triggers Zeabur container restart).
curl -s -o /dev/null -w "%{http_code}" http://localhost:9119/
# Should return 200
Relevant config.yaml sections:
hooks:
pre_llm_call:
- command: /opt/data/scripts/warm-memory-hook.py
timeout: 5
pre_tool_call:
- command: /opt/data/scripts/block-web-extract.sh
matcher: web_extract
memory:
memory_enabled: true
user_profile_enabled: true
These setups are specific to this VPS environment. Adapt paths and ports for your instance.