⚙ GENERATED FILE — do not edit. Edit empire-site/MANUAL.md and run build_manual.py to regenerate.
CFM Business · Hermes Agents

Hermes Agent Master Manual

Build one agent, parameterized to scale to 10,000 — reference + beginner quick-start in one document.
Single source of truth: MANUAL.md · Authoritative current standards: PART 0 §0.2

> ✅ CLEANED & CURRENT — 2026-07-04. The live instructional content (PART 0–4, PART 6 quick-start) has been brought to current reality: MiniMax-M3 (OpenRouter/Gemini/DeepSeek/Mistral provider walkthroughs removed) · canon_shim memory (60K/40K, Honcho gone) · scraper-stack · Twilio+Retell phone (Telnyx procedure removed). PART 5: Problems & Solutions is a dated HISTORICAL INCIDENT ARCHIVE — it intentionally references now-retired models/providers as a record of past fixes; do NOT re-apply its model/provider steps. For any conflict, PART 0 §0.2 (Current Standards) is authoritative. Format & colors are final.

This is now the ONE canonical copy. Six other copies of this manual (a legacy standalone HTML on 3 branches, a stale Google Doc, and a hand-authored section embedded in the live dashboard) have been retired or flagged for retirement — see decisions/DECISIONS.md (2026-07-04 entry). Edit only this file; MANUAL.html / MANUAL.for-docs.html are generated from it by build_manual.py — never hand-edited. ⚠️ Every cron in §0.2 below was invalidated by the 2026-06-30 cron wipe (trigger.dev overreach deleted 24 of Lisa's crons; 9 were restored 2026-07-02, King-approved, and are NOT the same 9 that existed before). The cron list below is rewritten from that live-VPS-verified restore, not from memory of the pre-wipe state.

PART 0 — THE HERMES AGENT MANUFACTURING BLUEPRINT

Step-by-step procedure to build ONE agent, PARAMETERIZED to scale to 10,000. This is the AUTHORITATIVE build process. Where it conflicts with the legacy Phase guide below, THIS WINS. Last verified: 2026-07-04 (reconciled against agents/lisa/profile.yaml, VPS-verified 2026-07-02 — the freshest ground truth in the project; see agents/_template/ for the full reproducible-template system this Part 0 blueprint now shares duties with). RULE: every step is tagged [VERIFIED] (built for Lisa and confirmed working) or [TODO] (needs detail/test). Nothing is "done" until [VERIFIED].

§0.1 — AGENT PARAMETERS (the template; fill these in per agent — this is what makes it reproducible)

• AGENT_NAME (e.g. "Lisa") • AGENT_ROLE (e.g. "executive assistant") • NICHE/VERTICAL • OWNER_NAME · OWNER_EMAIL · OWNER_TELEGRAM_ID · OWNER_PHONE • VPS: provider=Hostinger, plan=KVM2 (8GB), VPS_ID, VPS_IP • AGENT_EMAIL (free Gmail or domain alias) • TELEGRAM_BOT_TOKEN • PHONE: carrier=TWILIO (LOCKED), AGENT_PHONE_NUMBER, VOICE_AGENT_NAME (e.g. "IVY") • MEMORY: provider=canon_shim (canon stack source-of-truth + holographic local recall; Honcho REMOVED), bounded core MEMORY.md/USER.md, recall_mode=hybrid, cap 60K/40K • LLM: primary=MiniMax-M3 (provider=minimax, base_url https://api.minimax.io/v1). No fallback chain. Vision is auxiliary (provider=auto, model=auto — NOT pinned to a model name). → Building agent N = filling this block + running §0.3. No hand-assembly, no hardcoded "Lisa".

§0.2 — CURRENT STANDARDS (LOCKED 2026-06-07 — these CORRECT and SUPERSEDE every contradictory passage in the legacy manual below)

• LLM: primary MiniMax-M3 via the minimax custom provider, base_url https://api.minimax.io/v1. NO fallback chain. Valid model ids: MiniMax-M3 / MiniMax-M1 — do NOT use MiniMax-Text-01 (its 40K max_output is below the gateway's max_tokens, so it 400s). Vision is routed AUXILIARY (provider=auto, model=auto — NOT pinned to a model name; no separate Gemini/DeepSeek vision route). ⚠️ DEAD — never set: model id minimax3 (400 "unknown model"), endpoint api.minimax.chat (decommissioned → 401 "User not found"), gemini-2.5-flash, deepseek-v4-pro, gpt-5.3-codex, mistral-small, kimi-k2, OpenRouter, or direct-anthropic. [ALL legacy "Sonnet via OpenRouter", "DeepSeek v3", and "gemini-2.5-flash" sections below are OBSOLETE.] • WEB SCRAPING: canonical tool is scraper-stack (/opt/data/scripts/scraper-stack/scrape.py) — a tiered router that goes free-first (Tier 0 requests/BeautifulSoup → Tier 1 local Crawl4AI headless) and only uses paid APIs on a hard block (Tier 2 ScraperAPI/Zyte → Tier 3 Firecrawl LLM-extract). Replaces the scattered legacy scrape scripts. Reads keys from /opt/data/.env. Tier 1 uses a PINNED Chromium at /opt/data/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome (override env SCRAPER_STACK_CHROME_BIN) — the pin keeps JS pages on the FREE tier instead of silently falling through to paid ScraperAPI. (General pattern for any tool that bundles its own Chromium.) • PHONE CARRIER: TWILIO ONLY (Telnyx is BANNED — purge every legacy Telnyx reference). IVY's live number is +1-817-632-6536, a Twilio number PROVISIONED THROUGH RETELL (the original Telnyx number 817-973-0830 was banned and replaced June 2026). ⚠️ NUANCE: there are NO direct Twilio API keys (TWILIO_ACCOUNT_SID / AUTH_TOKEN / PHONE) in /opt/data/.env — the number lives at Retell's phone-provisioning layer, not via a local Twilio SDK. Voice agent IVY is Retell-resident (api.retellai.com), not a process on this VPS. SMS alerts to King go via a Gmail→carrier gateway: 9455452529@vtext.com (Verizon). [TODO: rewrite legacy Section 13 for Twilio + IVY + Retell.] • VOICE I/O: STT (voice-memo transcription) = provider groq, model whisper-large-v3-turbo (LIVE since 2026-06-22; before this, voice memos arrived as silent attachments). TTS = provider edge, voice en-US-AriaNeural (fallbacks configured: elevenlabs, openai, xai, minimax). [This is why a groq provider block exists in config.yaml — it is for STT, not the chat model.] • CRONS (9 active, verified live 2026-07-02, restored post-wipe — keep under the Rule of 30): nightly-github-backup (06:00 daily, backs up /opt/data to GitHub), daily-credit-report (13:00 daily), morning-health-check (13:00 daily, Cloudflare tunnel health + self-heal), daily-diagnostic (11:00 daily, zero-LLM infra report), google-token-refresh (every 50m), kanban-watchdog (/5 — NOTE: the kanban board itself is RETIRED in favor of ClickUp; this watchdog is a retirement candidate, not a live dependency), ensure-retell-handler (/5, supervisor for the :8643 call handler), ensure-inbox-handler (/5, supervisor for the email inbox-wake handler), validate-critical-keys (hourly). ⚠️ This entire list replaces the pre-2026-06-30 set below the wipe line — none of the earlier canon-heartbeat / email-triage-live / ghl-refund-monitor / lisa-broker-watch / memory-curator cron names survived the wipe+restore; the memory curator now runs built-in rather than via its own cron (watchdog_cron_discrepancy flagged in agents/lisa/profile.yaml). High-frequency work runs as daemons + a light supervisor cron, never a cron that does the work. • PORTS: gateway 8642 (127.0.0.1, docker-proxy) · broker 4097 (host, 0.0.0.0) · 8643 serves THREE path-routed jobs inside the container (/retell Retell fn-handler, /broker-push wake receiver = 172.16.1.2:8643, /ivy_brain webhook) · OpenCode push 127.0.0.1:4098/push · container also publishes 4860 = ttyd (pid 7, Hermes Agent web-terminal — browser-based shell to the container, basic-auth enabled, 0.0.0.0, NOT in Cloudflare tunnel as of 2026-06-26). 9118 = Hermes dashboard (pid 262, hermes dashboard --port 9118 --host 127.0.0.1 --no-open --skip-build --insecure) and 9119 = auth proxy (pid 288, dashboard_auth_proxy.py) — BOTH LIVE as of 2026-06-26 re-verify (Lisa). Prior 2026-06-23 DOWN note was stale; these are the active Hermes web UI surfaces. • COMMS MESH: the bilateral-broker systemd service runs /opt/data/scripts/broker.py with KNOWN_AGENTS = {"opencode", "lisa", "code", "kim"} → QUAD-AGENT MESH (wired 2026-06-26). ⚠️ SUPERSEDED for Kim, 2026-07-01 (reconciled 2026-07-04):* this line originally said PUSH endpoints exist only for lisa and opencode, with kim POLL-ONLY — that is now WRONG. Kim has had a real push endpoint since 2026-07-01 (http://172.16.2.2:8643/broker-push — note the corrected IP, .2 not .1; see "The Trigger.dev Durable Delivery Spine" section for the full current mesh topology). Her old poll path (kim-send, 172.16.2.1:4097) still exists as a backstop cron, not her primary path anymore. code remains genuinely POLL-ONLY (NAT'd workstation, no push handler possible) — that part of this line is still accurate. code polls via code_broker_poller.py, reached for delivery via the gateway API on 8642. Kim subnet: 172.16.2.0/24, firewall rule in /opt/data/scripts/broker-firewall.sh (boot-persistent). Plus a Code↔OpenCode file bridge at ~/.hermes-bridge/. ⛔ DRIFT TRAP: a STALE bilateral copy of broker.py exists at /docker/hermes-agent-jemg/data/scripts/broker.py ({"opencode","lisa"}) — it is the ORPHAN, never loaded; do NOT copy it over the live /opt/data one or you kill code and kim routing. • MEMORY: provider=canon_shim (LIVE 2026-06-23, commit 80c205e). Bounded core (MEMORY.md/USER.md, cap raised to 60K/40K from 15K/12K) + holographic provider (local SQLite runtime recall) backed by the canon stack (scripts/canon/) as source-of-truth, recall_mode set to hybrid (NOTE: hybrid recall — G6 FTS5 + sqlite-vec/FastEmbed — is PLANNED, NOT yet wired; current live recall is FTS5-only, the S2 canon_shim→RecallIndex prefetch does not exist yet), plus a nightly auto-extraction pipeline. Under canon_shim, fact_store WRITES are gated → pending proposals for review, READS delegate to holographic; canon_verify GREEN. ⚠️ The canon-heartbeat cron this section used to cite did NOT survive the 2026-06-30 wipe+restore (see §0.2 CRONS above) — memory drift monitoring is currently a gap, not an active safeguard; the curator itself still runs built-in. Phase B (flip reads to canon) is DEFERRED + King-gated. [Honcho REMOVED/DEPRECATED 2026-06-20 — uncorrectable resurrection bug; Lisa migrated off it, verified healthy.] • AGENT↔CODE COMMS: email, and ALWAYS CC the shared Gmail (cfmbusiness@gmail.com) so the agent can read the full message body via the Gmail API (Hostinger IMAP body-read is unreliable). [Root cause verified 2026-06-07.] • OWNERSHIP: all writes to /opt/data as the hermes user (never root). Provisioning/lifecycle via the Hostinger VPS API (programmatic), not manual dashboard clicks.

• VPS-NATIVE FLEET (LOCKED 2026-06-26 — the rule that lets this scale to 10,000 agents): Nothing fleet-critical runs on King's laptop. Agents are BORN on the VPS (the new-agent-onboard factory provisions them there); fleet services (memory curator, PM engine, fleet analytics) are VPS systemd, addressable by agent-ID — never Windows Task Scheduler, never a laptop path. King's laptop is a remote control, not the engine. NUANCE: King's PERSONAL tooling (e.g. insight-XL personal-session analytics) MAY be laptop-native because its data is generated there — but that data MUST be centralized (synced to the VPS/central store) so it follows King across machines. Litmus test: "if King swaps laptops, does this keep running AND see all its data?" If no → not VPS-native enough.

§0.3 — BUILD PHASES (each: GOAL · STEPS · VERIFY · STATUS)

EXECUTOR: /new-agent-onboard skill (built 2026-06-26, pm-rebuild/ws6-agents/new-agent-onboard/). Invoke it to run these phases interactively — Phase 7 (broker wire) is fully automated (proven on Kim); Phases 1–6 are grounded in this manual with a few ⚠️ CONFIRM placeholders being filled from Manual SECTION 4. Dry-run by default (LIVE=1 to execute). This is the agent factory — do NOT hand-assemble agent N+1.

PHASE 1 — PROVISION VPS. Goal: a running KVM2 box. Steps: Hostinger VPS API create + set root password (programmatic). Verify: API GET shows state=running. STATUS: [VERIFIED control plane works 2026-06-07; TODO: script end-to-end create]. PHASE 2 — INSTALL HERMES. Goal: gateway running. Steps: scripted docker run of nousresearch/hermes, mount /opt/data. Verify: docker ps + dashboard reachable. STATUS: [TODO: convert manual Phase 4 to a script]. PHASE 3 — LLM CONFIG. Goal: primary model live. Steps: hermes config set model.default MiniMax-M3; provider minimax (base_url https://api.minimax.io/v1 set in config.yaml → providers.custom:minimax.base_url); NO fallback chain. Verify: hermes config get model.default + a live reply. STATUS: [VERIFIED on Lisa]. PHASE 4 — IDENTITY. Goal: agent persona loaded. Steps: render SOUL.md + USER.md from templates using §0.1 params; fix the SOUL.md symlink to point at the real file (legacy Problem 56). Verify: agent introduces itself correctly; SOUL.md non-empty. STATUS: [VERIFIED — /new-agent-onboard executor (pm-rebuild/ws6-agents/new-agent-onboard/) handles Phase 4 automatically with per-agent params from §0.1 interview, 2026-06-26]. PHASE 5 — MEMORY. Goal: semantic + person-aware memory. Steps: hermes memory setup → canon_shim provider (canon stack + holographic; Honcho is GONE); recall_mode hybrid; cap is PER-AGENT (Lisa=60K/40K, Kim=80K — set at provision time, never migrate debt from another agent); deploy curator daemon (900s interval, 75% high-water) + supervisor cron + canon-heartbeat (every 30m). Verify: hermes memory status shows provider active + canon_verify GREEN + curator 7/7 audit PASS. STATUS: [VERIFIED on Lisa (60K) + Kim (80K) — Phase A; Phase B read-flip King-gated]. PHASE 6 — TELEGRAM. Goal: owner can talk to agent. Steps: BotFather token → hermes config. Verify: round-trip message. STATUS: [carry from legacy Phase 7]. PHASE 7 — COMMS BRIDGE. Goal: Code↔agent autonomous email. Steps: agent reads cfmbusiness via Gmail API; Code emails agent CC cfmbusiness; (optional) cloud routine to wake Code. Verify: real round-trip with substance, not just ack. STATUS: [PARTIAL — root cause fixed 2026-06-07, CC-always rule]. PHASE 8 — VOICE AGENT (IVY). Goal: outbound-capable voice agent. Steps: Retell + TWILIO trunk; dynamic begin-message (outbound persona); /think, /place_call, /save_task; anti-hallucination prompt V3+. Verify: live outbound test call with correct opening. STATUS: [TODO: write from current IVY blueprint; purge Telnyx]. PHASE 9 — QA / SMOKE TEST. Goal: confirm the agent works before handoff. Steps: python scripts/canon/reliability_gate.py --selftest (6 gates: LLM responds, memory healthy, Telegram reachable, canon clean, curator running, broker wired) + curator audit 7/7. Verify: all gates GREEN. Agent is SHIP-ELIGIBLE only after this passes. STATUS: [VERIFIED — reliability_gate.py is the checklist; proven on Lisa (6/6) + Kim (6/6), 2026-06-26].

§0.4 — KNOWN MISTAKES & FIXES (so agent N+1 never repeats them)

TRAP 1 — Dead MiniMax endpoint / model. Symptom: 401 "User not found" or 400 "unknown model" on every Lisa reply. Cause: api.minimax.chat is DECOMMISSIONED (→ 401); model id minimax3 does not exist (→ 400); MiniMax-Text-01 400s because its 40K max_output < gateway's max_tokens. Fix: base_url: https://api.minimax.io/v1; model MiniMax-M3. ⚠️ Valid ids: MiniMax-M3, MiniMax-M1, MiniMax-M2. Never use minimax3, api.minimax.chat, or MiniMax-Text-01.

TRAP 2 — Broker orphan. Symptom: added an agent to KNOWN_AGENTS but messages misroute or agent is unknown. Cause: edited the STALE container copy at /docker/hermes-agent-jemg/data/scripts/broker.py instead of the live HOST copy at /opt/data/scripts/broker.py. The container copy is an orphan — systemd never loads it. Fix: ALWAYS edit /opt/data/scripts/broker.py (HOST). After editing, systemctl restart bilateral-broker on the host.

TRAP 3 — Port collision. Symptom: new agent container won't start, or an existing service dies silently. Cause: the new agent's ports clash with a live service. Fix: before docker run, check docker ps + ss -tlnp on the host. Known assignments: 8642=Lisa gateway, 8643=Lisa retell+broker-push, 4097=broker, 4098=opencode listener, 4860=ttyd, 4861=Kim gateway, 8644=Kim gateway (host-local — ⚠️ confirmed unreachable 2026-07-04, curl 127.0.0.1:8644/health → connection refused; Kim's gateway binds to localhost INSIDE her own container, so even though Docker publishes container-port 8642→host-port 8644, the host still can't reach it — this is why Kim needed her own push-receiver process instead, see §0.5 mesh notes), 9118=dashboard, 9119=auth proxy.

TRAP 7 — Claude Code's own safety classifier blocks cross-service credential moves silently. Symptom: an action (editing .claude/settings.json permissions, or moving an API key from one service to another, e.g. uploading a live gateway key to a third-party cloud task runner) is auto-blocked with no prompt, even though the Bash allowlist covers it. Cause: Claude Code has a safety classifier separate from the project's own permission allowlist — allowlist entries do NOT override it, and it can flag cross-service credential moves as "[Data Exfiltration]" even when legitimate. Fix: switch Claude Code's permission mode from "Auto mode" to "Default"/"Ask before edits" to get an actual approval prompt, do the action, switch back if needed. (Hit 2026-06-30/07-01 while wiring Lisa's gateway key into trigger.dev's prod env vars.)

TRAP 4 — Secrets in tracked files. Symptom: Telegram bot token, API key, or password visible in git history. Cause: writing config to PROGRESS.md, SOUL.md, or any git-tracked file instead of .env. Fix: ALL tokens and secrets go in the agent's /opt/data/.env (inside container) or .env in the Hermes Agents repo (.gitignore'd). Never commit them. If already committed: git filter-repo or scrub + force-push. (Caught live 2026-06-26: Kim's Telegram bot token was in PROGRESS.md; scrubbed by AI project management tab. Recurred 2026-07-03: Lisa's live Telegram bot token was hardcoded in 5 tracked files — not just the 2 originally flagged — including scripts/health-check-cron.sh and scripts/watch-code-inbox.ps1; the Hostinger API token was similarly live in 3 tracked files including PROGRESS.md itself. Both scrubbed to .env-only lookups; rotation of both tokens is King-owed and tracked in .tmp/SECRETS_TO_FIX.md. Two occurrences of the same trap is a pattern, not a fluke — a pre-commit secret scanner would catch this class of bug before it lands; worth adding as a TODO.)

TRAP 5 — New agent subnet not in firewall. Symptom: kim-send health returns connection refused; agent can't reach broker on port 4097. Cause: each agent container is on its own Docker network (172.16.X.0/24); only Lisa's subnet (172.16.1.0/24) was in the original firewall rule. Fix: ufw allow from <SUBNET>/24 to any port 4097 + iptables -I DOCKER-USER -s <SUBNET>/24 -p tcp --dport 4097 -j ACCEPT + append to /opt/data/scripts/broker-firewall.sh for boot persistence.

TRAP 6 — No-migration-debt rule. Symptom: new agent starts with stale facts, old identity bleed, wrong memory caps from a prior agent's snapshot. Cause: copying Lisa's memory files instead of seeding fresh. Fix: every new agent gets a FRESH empty memory on day one — same 4-layer architecture (canon_shim, curator, cap per §0.1), zero content from Lisa. Kim was the reference build: "No migration debt — started on final architecture." (Honcho's resurrection bug was exactly this: identity bleed from stale memory.)

[TODO: migrate verified lessons from Sessions 24–57 (root-owned /opt/data outages, cloudflared not auto-starting, paid-model budget drain, Hostinger key-only SSH, email body-read failure) — full traps in new-agent-onboard SKILL.md §0.4 section.]

§0.5 — CURRENT DEPLOYMENT STATE (live truth, verified 2026-06-25; reconciled 2026-07-04)

This section is the live state of the FIRST agent (Lisa) and the team around her — verified against the running VPS, not from memory. It supersedes any contradictory legacy passage below.

⭐ Since 2026-07-02 there is a more granular, actively drift-checked version of this exact section for Lisa (and Kim) at agents/lisa/profile.yaml + narrative.md — the Hermes Agent Template (Phase 1). It's checked against the live VPS by a daily automated drift-checker (scripts/agent_template_drift.py, flags-never-overwrites) rather than manual re-verification. When this §0.5 and the template disagree, trust the template — it's newer and machine-checked. Building agent N+1 should start from the template (Kim was instantiated from it in ~7 minutes), not from copy-pasting this section.

THE TEAM — 7 agents (genders LOCKED 2026-06-19):

Agent Gender Role Model Where it runs
Lisa F COO / executive assistant MiniMax-M3 VPS (Hermes) port 8642
Kim F Growth & Marketing / AutomateYourBiz MiniMax-M3 VPS (Hermes) port 4861/8644 — wired 2026-06-26
Ivy F Phone receptionist / voice GPT-5.1 Retell platform — +1-817-632-6536
Claude Code M Tech lead / workstation engineer Claude Opus 4.8 Workstation (VS Code)
Claude Cowork M Desktop assistant TBD Workstation
OpenCode M VPS engineer opencode/deepseek-v4-flash (Code/King-verified, not VPS-checkable) VPS (OpenCode CLI)
Kode F VPS control plane TBD VPS control
Claude in Chrome M Browser worker Claude Sonnet Chrome extension

⚠️ Kode is the VPS control plane — the Hostinger API key is held by Code, NOT Lisa. (This is the exact spot where Kode gets dropped from the roster; the pointer is deliberate.)

MEMORY — canon stack (durable truth):

KIM MEMORY — verified 2026-06-26:

BACKUP / SYNC: /opt/data/scripts/github-sync.sh — nightly backup of critical files to a private GitHub repo; 3-retry push with exponential backoff; Telegram alert on FINAL failure only.

OPERATIONAL HEALTH WARNINGS (live):

HERMES AGENT MASTER MANUAL — CFM BUSINESS Version 4.0 FINAL | May 2026 Single Source of Truth — Edit This Document Directly. Never Create a New Doc. Google Doc ID: (this document)

TABLE OF CONTENTS

PART 1: UNDERSTANDING HERMES Section 1: What Is a Hermes Agent? Section 2: Internal Makeup — Architecture & Components 2A. The Five Pillars 2B. Memory System (USER.md + MEMORY.md) 2C. Skills System & Progressive Disclosure 2D. Soul (SOUL.md) 2E. Cron Jobs — Basic & Advanced 2F. LLM Providers & Models 2G. Session Storage & Search (SQLite)

PART 2: SETUP GUIDE Section 3: Pre-Setup Requirements & Security Preparation Section 4: Phase-by-Phase Setup (Phases 1–10) Section 5: Browser Infrastructure Setup (Phase 11 — 11 Sub-Phases)

PART 3: USE CASES Section 6: Personal Life Use Cases Section 7: Business Life Use Cases

PART 4: OPERATIONS Section 8: Dashboard Guide (Kanban, Sessions, Platforms, Memory, Crons) Section 9: Telegram Interface & Context Window Management Section 10: Scaling Decision Tree & Maintenance Section 11: Security Best Practices

PART 5: PROBLEMS & SOLUTIONS Section 12: 59 Common Problems with Step-by-Step Solutions (includes Quick Index)

APPENDIX: Glossary of Terms

PART 1: UNDERSTANDING HERMES

SECTION 1: WHAT IS A HERMES AGENT?

Hermes is an open-source autonomous AI agent created by Nous Research. It is one of the most widely adopted AI agent frameworks in the world, with over 140,000 GitHub stars and an MIT license — meaning it is completely free to use and modify.

DEFINITION: Autonomous AI Agent — An AI system that doesn't just answer questions but takes independent actions in the real world: browsing websites, sending messages, managing files, running scheduled tasks, and more — without a human needing to be present for each action.

Unlike conversational tools like ChatGPT or Claude, Hermes is designed to ACT. You give it a goal, connect it to tools, and it works toward that goal on your behalf — 24 hours a day, 7 days a week — running on a server that never turns off.

KEY COMPARISON:

THE CFM DEPLOYMENT: At CFM Business, Hermes agents are deployed on Hostinger VPS servers (cloud computers that run 24/7). They are controlled primarily through Telegram. The first agent deployed is Lisa, who handles Upwork candidate screening and recruitment pipeline tasks.

KEY FACTS:

WHY A VPS AND NOT YOUR OWN COMPUTER? Your personal computer turns off. A VPS runs continuously in a data center. This means your agent can execute cron jobs at 3am, respond to Telegram messages while you sleep, and process tasks over the weekend — all without any action from you.

HERMES ON ANDROID (TERMUX): For personal experimentation, Hermes can run directly on an Android phone using Termux (an Android terminal emulator). Install from F-Droid (NOT Google Play — the Play version is outdated), then install Python, Node.js, and Git. Not recommended for production use: the phone must stay on and connected, and RAM is limited. Good for testing concepts before committing to a VPS.

SECTION 2: INTERNAL MAKEUP — ARCHITECTURE & COMPONENTS

Understanding Hermes's internal structure lets you customize it correctly and troubleshoot it when things go wrong. Everything about how the agent thinks, remembers, and acts comes down to five core pillars.

SECTION 2A: THE FIVE PILLARS OF HERMES

PILLAR 1 — MEMORY The agent's persistent knowledge about you, your preferences, and important context. Memory survives every session restart, container restart, and VPS reboot — because it lives in text files on disk, not in RAM. Two files: USER.md (permanent facts) and MEMORY.md (working memory that evolves). The agent reads both at the start of every conversation.

PILLAR 2 — SKILLS Pre-written playbooks that tell the agent how to handle specific task types. Stored as .md files in ~/.hermes/skills/. The key innovation is progressive disclosure: at startup, Hermes reads only the YAML metadata from each skill file (not the full content), so it knows what skills exist without loading everything into the context window. Full skill content loads only when a skill is triggered.

PILLAR 3 — SOUL The agent's identity, personality, values, and behavioral rules. Stored in SOUL.md. This is what makes "Lisa" different from "a generic AI assistant." It defines the agent's name, communication style, what it will and won't do, and how it handles edge cases.

PILLAR 4 — CRONS Hermes's scheduling system. Cron jobs run automatically at set times — no human needs to start a conversation. This is what makes the agent truly autonomous. A cron can check emails every 30 minutes, generate a daily report at 5pm, or run any task on any schedule.

PILLAR 5 — SELF-IMPROVING LOOP Hermes can analyze past conversations, identify gaps in its knowledge, and update its own MEMORY.md. This makes the agent progressively smarter about your preferences, projects, and work style without manual updates from you.

SECTION 2B: MEMORY SYSTEM IN DETAIL

⚠️ SUPERSEDED for current standards (2026-06-23) → see PART 0 §0.1/§0.2 + PHASE 5. Memory is now provider=canon_shim (canon stack source-of-truth + holographic recall, bounded-core cap 60K/40K). Honcho is REMOVED. The USER.md/MEMORY.md mechanics below are still accurate; any Honcho references or old cap numbers (15K/12K) are OBSOLETE. Kept as historical record.

Hermes uses two memory files stored at /opt/data/ inside the Docker container:

USER.md — PERMANENT MEMORY Facts about the user that almost never change. The agent reads this at every conversation start.

Contents to include:

Example USER.md: ---

User Profile

Name: [Your Name] Preferred Name: [What to call you] Location: [City, State] Timezone: Central (CDT, UTC-5)

Communication Preferences

Professional Context

Company: CFM Business Role: [Your role] Focus: Upwork staffing, recruitment, business operations

Current Projects

Important Rules

---

MEMORY.md — WORKING MEMORY Evolving information that updates regularly. The agent writes to this after significant sessions.

Contents:

FILE LOCATIONS: /opt/data/USER.md /opt/data/MEMORY.md

CRITICAL: If these files do not exist, the agent starts every conversation as if meeting you for the first time. Creating these files is one of the most important setup steps.

HOW MEMORY PERSISTS: The /opt/data/ directory is mounted from the VPS host into the Docker container (via -v /opt/data:/opt/data in the docker run command). Even if the container is deleted and recreated, data in /opt/data/ survives.

SECTION 2C: SKILLS SYSTEM & PROGRESSIVE DISCLOSURE

Skills are the agent's playbooks — step-by-step instructions for specific task types. They live as markdown files in ~/.hermes/skills/.

SKILL FILE STRUCTURE: Every skill has two parts:

  1. YAML FRONT MATTER (between --- markers at top of file):

Metadata the agent reads at startup to know the skill exists and when to use it.

  1. SKILL BODY:

The actual step-by-step instructions. Only loaded when the skill is triggered.

Example skill file: --- --- name: reply-to-upwork trigger: "When asked to check or reply to Upwork messages" description: "Handles Upwork inbox monitoring and candidate reply workflow" requires: [browser, proxy] ---

Reply to Upwork Candidates

Objective

Check the Upwork inbox and reply to new candidate messages using approved templates.

Steps

  1. Open browser and navigate to upwork.com
  2. Log in using saved session cookies
  3. Navigate to Messages > Inbox
  4. Read all unread messages
  5. Categorize each: Hot (strong candidate), Warm (possible), Cold (not a fit)
  6. For Hot candidates: reply with intro template from /opt/data/templates/hot_reply.md
  7. For Warm candidates: reply with screening questions template
  8. For Cold candidates: send polite pass template
  9. Log all actions to MEMORY.md
  10. Send summary to user via Telegram

Templates Location

/opt/data/templates/

Error Handling

---

PROGRESSIVE DISCLOSURE EXPLAINED: When Hermes starts, it reads ONLY the YAML headers of all skill files. This means:

This allows you to have 20+ skills without the agent starting every conversation with a bloated context window.

SKILL PRECEDENCE: Skills override SOUL.md for their specific domain. If SOUL.md says "always ask for confirmation before sending messages" but the Upwork skill says "send replies autonomously," the skill wins when it's active.

HOW TO CREATE A SKILL:

  1. SSH into VPS: ssh root@YOUR_VPS_IP
  2. Enter container: docker exec -it hermes bash
  3. Navigate: cd ~/.hermes/skills/
  4. Create file: nano my-new-skill.md
  5. Add YAML front matter (name, trigger, description, requires)
  6. Write step-by-step instructions
  7. Save (Ctrl+O, Ctrl+X)
  8. Test: Tell the agent the trigger phrase in Telegram

AGENTS.MD — LOCAL PROJECT CONTEXT: When using Hermes via terminal (CLI mode) inside a specific project folder, you can create an agents.md file in that directory. This is similar to CLAUDE.md — it gives the agent project-specific context without cluttering global memory. The agent reads agents.md automatically when running CLI sessions in that directory.

SECTION 2D: SOUL (SOUL.md)

SOUL.md is the agent's identity file. It defines who the agent is at a fundamental level.

What SOUL.md contains:

Example SOUL.md opening: --- You are Lisa, an autonomous recruitment assistant for CFM Business. Your primary role is managing the Upwork hiring pipeline — screening candidates, sending templated replies, and keeping the pipeline moving.

You communicate professionally but warmly. You are proactive: if you notice something important, you flag it without being asked. You are honest about your limitations.

You never take irreversible actions (sending emails, making purchases, deleting files) without explicit confirmation unless you have been given specific autonomous permission for that action type. ---

IMPORTANT — MODEL CHOICE: The CFM standard model is MiniMax-M3 (via the minimax provider), which respects SOUL.md instructions. If an agent ever ignores its custom personality or behavioral rules, first confirm the model is MiniMax-M3 and the provider is minimax (some retired models — e.g. safety-filtered GPT-class models — would override SOUL.md). See PART 0 §0.2.

SOUL.md file location: /opt/data/SOUL.md

SECTION 2E: CRON JOBS — BASIC & ADVANCED

Crons are what make Hermes truly autonomous. They run tasks on a schedule without any human input.

BASIC CRON SYNTAX: Uses standard Unix cron format: minute hour day month weekday

Examples:

Creating a cron: docker exec -it hermes hermes cron add \ --schedule "0 9 *" \ --prompt "Check my Upwork inbox and summarize new candidate messages"

ADVANCED CRON FLAGS:

context_from: [filepath] Loads a file's contents as additional context before the cron runs. Use case: Cron needs campaign-specific info without hardcoding it. Example: --context_from "/opt/data/weekly_context.md"

work_dir: [directory path] Sets the working directory for the cron session. Use case: Cron works with files in a specific project folder. Example: --work_dir "/opt/data/reports/"

no_agent: true Runs as a script without LLM reasoning. For tasks that don't need AI. Use case: File cleanup, log rotation, health checks. Example: --no_agent true

CRITICAL CRON RULE: Cron sessions CANNOT create new cron jobs. If a cron tries to create another cron, it fails silently. Cron creation must happen in a regular conversation session or via the dashboard.

CRON TIMEZONE NOTE: Hermes uses UTC by default. If you're in Central Time (UTC-5 in summer, UTC-6 in winter), add 5-6 hours to get UTC time:

SECTION 2F: LLM PROVIDERS & MODELS

The current LLM standard is MiniMax-M3 via the minimax provider (base_url https://api.minimax.io/v1), NO fallback chain — see PART 0 §0.2 for the authoritative config and §0.5 for live state. The legacy OpenRouter / Claude-Sonnet / DeepSeek / Gemini / Mistral provider walkthrough that lived here has been removed; CFM no longer uses that stack.

SECTION 2G: SESSION STORAGE & SEARCH (SQLite)

Every Hermes conversation is stored permanently in a SQLite database. This is a powerful and often overlooked capability.

DATABASE LOCATION: /opt/data/sessions.db

WHAT IS STORED:

HOW TO SEARCH SESSION HISTORY: From within a Hermes conversation (in Telegram), ask:

The agent queries the SQLite database and surfaces relevant past sessions.

WHY THIS MATTERS FOR CFM: Troubleshooting recurring issues: Ask the agent "When did the proxy last fail and what fixed it?" and it will find that session in history. Auditing: Confirm exactly what was sent to candidates and when. Continuity: Even if context window compacts, you can search for past work.

AUTO-COMPACTION: When a conversation reaches approximately 136,000 tokens, Hermes automatically summarizes and compresses older portions of the conversation. This allows conversations to continue past the model's context limit.

IMPORTANT: This happens more visibly in the CLI than in Telegram. In Telegram, auto-compaction may occur without obvious notification. The session is still stored in SQLite — you can always search back through history even if the current conversation has been compacted.

PART 2: SETUP GUIDE

SECTION 3: PRE-SETUP REQUIREMENTS & SECURITY PREPARATION

REQUIRED ACCOUNTS (create before starting):

  1. Hostinger account (hostinger.com) — for the VPS server
  2. MiniMax account (api.minimax.io) — for LLM access (model MiniMax-M3)
  3. Telegram account — for the bot interface
  4. GitHub account — for agent backup (PRIVATE repository)
  5. Dedicated Gmail — one new account just for this agent (NOT your personal email)

REQUIRED INFORMATION:

SECURITY PREPARATION — DO THESE BEFORE SETUP:

STEP A: Create a dedicated email for this agent Create a brand new Gmail account specifically for this agent (e.g., lisacfm2026@gmail.com). REASON: If the agent's email is ever compromised, only that account is affected. Your personal and business emails stay safe. The agent needs an email for GitHub notifications, cron alerts, and integrations — never use your primary email for this.

STEP B: Plan your API key naming strategy When you create your MiniMax API key, name it specifically: Example: "Lisa-CFM-Upwork-Agent" (NOT "my key" or "key 1") REASON: Named keys let you see per-agent spending in your dashboard and revoke access for one agent without disrupting others.

SECTION 4: PHASE-BY-PHASE SETUP (PHASES 1–10)

PHASE 1: PROVISION YOUR VPS (HOSTINGER)

Step 1.1 — Log into Hostinger Go to hpanel.hostinger.com and log in.

Step 1.2 — Navigate to VPS Hosting Top menu: VPS → VPS Hosting → Order New VPS

Step 1.3 — Select the KVM 2 Plan Specifications:

This is sufficient for 1–2 Hermes agents running simultaneously.

Step 1.4 — Configure Your VPS

Step 1.5 — Enable Free Malware Scanner (Optional) During VPS setup, Hostinger offers a free malware scanner. Enable it if the option appears — it provides baseline security monitoring at no extra cost.

Step 1.6 — Wait for Provisioning VPS setup takes 2–5 minutes. You'll receive an email with your server's IP address.

Step 1.7 — Save Your Server Details Record in your local .env file (NEVER in Git or chat):

[SCREENSHOT: Hostinger VPS dashboard showing newly provisioned server with IP address]

PHASE 2: CONNECT TO YOUR VPS VIA SSH

Step 2.1 — Open a Terminal

Step 2.2 — Connect ssh root@YOUR_VPS_IP

Step 2.3 — Accept the Host Key First connection shows a security warning about the host key fingerprint. Type "yes" and press Enter.

Step 2.4 — Enter Password Type your root password. Characters will NOT appear as you type — this is normal SSH behavior.

Step 2.5 — Verify Success You should see: root@hermes-cfm-lisa:~#

[SCREENSHOT: Terminal showing successful SSH connection prompt]

PHASE 3: UPDATE SERVER & INSTALL PREREQUISITES

Step 3.1 — Update all packages apt update && apt upgrade -y (This may take 2–5 minutes)

Step 3.2 — Install required tools apt install -y curl wget git unzip python3 python3-pip

Step 3.3 — Install Docker curl -fsSL https://get.docker.com | sh

Step 3.4 — Verify Docker docker --version Output should show: Docker version 24.x.x or higher

[SCREENSHOT: Terminal showing Docker version output]

PHASE 4: INSTALL AND START HERMES

Step 4.1 — Pull the Hermes Docker image docker pull nousresearch/hermes:latest (Downloads ~1–2GB, takes 3–5 minutes)

Step 4.2 — Create the persistent data directory mkdir -p /opt/data

Step 4.3 — Run Hermes docker run -d \ --name hermes \ --restart unless-stopped \ -v /opt/data:/opt/data \ -p 3000:3000 \ nousresearch/hermes:latest

Flag explanations: -d = Run in background (detached) --name hermes = Name the container --restart unless-stopped = Auto-restart on server reboot -v /opt/data:/opt/data = Mount data directory for persistence -p 3000:3000 = Expose web dashboard on port 3000

Step 4.4 — Verify Hermes is running docker ps Look for "hermes" with status "Up X minutes"

Step 4.5 — Access the dashboard In your browser: http://YOUR_VPS_IP:3000 You should see the Hermes login/setup page.

[SCREENSHOT: Hermes dashboard first-launch screen in browser]

PHASE 5: CONFIGURE THE HERMES DASHBOARD

Step 5.1 — Create admin credentials On first launch, Hermes prompts for admin username and password. Save these in your .env file: HERMES_ADMIN_USER=your_username HERMES_ADMIN_PASSWORD=your_password

Step 5.2 — Explore the dashboard sections (Full guide in Section 8 — Dashboard Guide) Key areas: Kanban Board, Session Viewer, Connected Platforms, Cron Manager, Memory Viewer

[SCREENSHOT: Hermes dashboard main screen with kanban board]

PHASE 6: CONFIGURE YOUR LLM PROVIDER

The current CFM standard is MiniMax-M3 via the minimax provider, with NO fallback chain. (See PART 0 §0.2 / PHASE 3 for the authoritative config.)

Step 6.1 — Get your MiniMax API key

  1. Go to api.minimax.io and create an account
  2. Create an API key and copy it

Step 6.2 — Add the key to Hermes docker exec -it hermes hermes config set MINIMAX_API_KEY "your-minimax-key-here" (Set the provider base_url in config.yaml: providers.custom:minimax.base_url=https://api.minimax.io/v1)

Step 6.3 — Set the model (no fallback chain) docker exec -it hermes hermes config set model.default MiniMax-M3 docker exec -it hermes hermes config set model.provider minimax

Step 6.4 — Verify docker exec -it hermes hermes config get model.default (should print MiniMax-M3; then send a live message to confirm a real reply)

[SCREENSHOT: Terminal showing hermes config with MiniMax-M3]

PHASE 7: CREATE AND CONNECT TELEGRAM BOT

Step 7.1 — Open Telegram, find @BotFather Search for "@BotFather" in Telegram (official blue checkmark account).

Step 7.2 — Create your bot Send: /newbot BotFather asks for:

  1. Display name: "Lisa CFM Agent"
  2. Username (must end in "bot"): "LisaCFMAgent_bot"

Step 7.3 — Save your bot token BotFather gives you a token like: 123456789:ABCdefGHIjklmNOPqrstuvWXYZ Save in .env: TELEGRAM_BOT_TOKEN=123456789:ABCdef...

Step 7.4 — Add token to Hermes docker exec -it hermes hermes config set TELEGRAM_BOT_TOKEN "your-token"

Step 7.5 — Start Telegram integration docker exec -it hermes hermes telegram start

Step 7.6 — Test Open Telegram, find your bot by username, send "Hello" — the agent should respond within seconds.

[SCREENSHOT: Telegram conversation showing bot responding to first message]

PHASE 8: SET UP GITHUB BACKUP

IMPORTANT: Use a CLASSIC personal access token (not fine-grained). Fine-grained tokens have per-repository permission restrictions that cause backup failures. Classic tokens with "repo" scope work reliably with Hermes.

Step 8.1 — Create a private GitHub repository Name it something like "lisa-hermes-backup". Set visibility: Private.

Step 8.2 — Generate classic token GitHub → Settings → Developer Settings → Personal Access Tokens → Tokens (classic) → Generate new token (classic)

Step 8.3 — Add to Hermes docker exec -it hermes hermes config set GITHUB_TOKEN "ghp_your-token" docker exec -it hermes hermes config set GITHUB_REPO "yourusername/lisa-hermes-backup"

Step 8.4 — Enable and test docker exec -it hermes hermes backup enable docker exec -it hermes hermes backup now Check your GitHub repo — files should appear.

[SCREENSHOT: GitHub repository showing backed-up agent configuration files]

PHASE 9: CONFIGURE MEMORY FILES

Step 9.1 — Enter the container docker exec -it hermes bash

Step 9.2 — Create USER.md nano /opt/data/USER.md

Paste and customize:

User Profile

Basic Information

Name: [Your Name] Preferred Name: [What to call you] Location: [City, State] Timezone: Central (CDT, UTC-5 summer / UTC-6 winter)

Communication Preferences

Professional Context

Company: CFM Business Focus: Upwork staffing and recruitment, business operations

Current Projects

[List your active projects here]

Agent Rules

Save: Ctrl+O, then Ctrl+X

Step 9.3 — Create MEMORY.md nano /opt/data/MEMORY.md

Initial content:

Agent Working Memory

Recent Activity

[Agent will update this automatically after sessions]

Current Tasks

[Agent will track ongoing work here]

Important Reminders

[Add anything you always want the agent to remember]

Known Issues

[Document any recurring problems here]

Save and exit.

Step 9.4 — Verify memory is working In Telegram: "What do you know about me?" The agent should reference your USER.md details.

[SCREENSHOT: Telegram showing agent referencing user profile from USER.md]

PHASE 10: CREATE YOUR FIRST SKILL AND CRON

Step 10.1 — Create a skill docker exec -it hermes bash nano ~/.hermes/skills/daily-summary.md

Paste: --- name: daily-summary trigger: "When asked for a daily summary or end-of-day report" description: "Generates summary of today's activities and tomorrow's priorities" requires: [] ---

Daily Summary Skill

Objective

Generate a concise daily summary for the user.

Steps

  1. Review MEMORY.md for today's logged activities
  2. Search session history for today's completed tasks
  3. Identify incomplete or pending items
  4. List top priorities for tomorrow
  5. Format and send via Telegram

Output Format

Save and exit.

Step 10.2 — Create a basic cron docker exec -it hermes hermes cron add \ --schedule "0 17 1-5" \ --prompt "Run the daily-summary skill and send me a summary of today's work"

This runs at 5pm Monday–Friday (adjust to your timezone: UTC + your offset).

Step 10.3 — Verify docker exec -it hermes hermes cron list You should see the cron with its schedule and next run time.

[SCREENSHOT: Terminal showing hermes cron list with new cron entry]

SECTION 5: BROWSER INFRASTRUCTURE SETUP (PHASE 11)

This section sets up autonomous web browsing for your Hermes agent. Required for tasks like Upwork monitoring, web research, and form submission.

WHAT YOU ARE BUILDING:

  1. Xvfb — virtual display (Chrome needs a screen to render to)
  2. Google Chrome — the browser itself
  3. CDP (Chrome DevTools Protocol) — how Hermes controls Chrome
  4. x11vnc + websockify — optional: lets you visually watch the browser
  5. Cloudflare Tunnel — secure access through firewall

WHY BOT DETECTION BLOCKS THE VPS: Websites like Upwork use Cloudflare and Forter bot detection. These systems identify datacenter IP addresses (which is what your VPS has) and block or challenge them. Use a real logged-in browser session (VNC manual login, saved cookies) or the site's official API instead.

--- Phase 11.1: Install Xvfb (Virtual Display) ---

apt install -y xvfb Xvfb :99 -screen 0 1920x1080x24 & export DISPLAY=:99

Make persistent (add to /etc/rc.local before "exit 0"): Xvfb :99 -screen 0 1920x1080x24 & export DISPLAY=:99

--- Phase 11.2: Install Google Chrome ---

wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb apt install -y ./google-chrome-stable_current_amd64.deb google-chrome --version # Verify

--- Phase 11.3: Install ChromeDriver ---

apt install -y chromium-chromedriver

--- Phase 11.4: Start Chrome with CDP ---

DISPLAY=:99 google-chrome \ --headless \ --disable-gpu \ --remote-debugging-port=9222 \ --no-sandbox \ --disable-dev-shm-usage &

Verify CDP is active: curl http://localhost:9222/json (Should return JSON with browser tab info)

--- Phase 11.6: Install x11vnc (Optional — Visual Browser Monitoring) ---

apt install -y x11vnc x11vnc -display :99 -bg -nopw -listen localhost -xkb

This lets you visually watch the browser using a VNC client.

--- Phase 11.7: Install websockify (Optional — Browser-Based VNC) ---

pip3 install websockify websockify --web=/usr/share/novnc/ 6080 localhost:5900 &

Access in browser: http://YOUR_VPS_IP:6080 (Lets you watch the agent's browser without installing VNC client software)

--- Phase 11.8: Set Up Cloudflare Tunnel (Secure Access) ---

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb dpkg -i cloudflared-linux-amd64.deb cloudflared tunnel login cloudflared tunnel create hermes-tunnel cloudflared tunnel run hermes-tunnel

--- Phase 11.9: One-Time Website Logins ---

Before autonomous browsing works, manually log in to each site once so session cookies are saved.

For Upwork:

  1. Connect via noVNC (http://YOUR_VPS_IP:6080) or VNC client
  2. You'll see Chrome running in the virtual display
  3. Navigate to upwork.com
  4. Log in with your credentials
  5. Cookies save automatically — future agent sessions use them

--- Phase 11.10: Connect Browser to Hermes ---

docker exec -it hermes hermes config set BROWSER_CDP_URL "http://localhost:9222"

--- Phase 11.11: End-to-End Test ---

In Telegram, send to your agent: "Open a browser, go to google.com, take a screenshot, and show me."

If working: Agent returns a screenshot of Google. If failing: See Problem 7 in Section 12 (troubleshooting).

[SCREENSHOT: Telegram showing agent returning a screenshot of Google homepage]

PART 3: USE CASES

SECTION 6: PERSONAL LIFE USE CASES

USE CASE 1: MORNING BRIEFING Daily cron that sends a Telegram message at your preferred wake time.

Contents:

Setup: Daily cron at your wake time (remember UTC conversion) Example prompt: "Generate my morning briefing: weather, any calendar events today, yesterday's incomplete tasks, and suggest my top 3 priorities for today."

---

USE CASE 2: EMAIL TRIAGE Monitor inbox, categorize by importance, summarize and alert.

Setup: IMAP connection to your personal Gmail (App Password) Cron: Every 30–60 minutes Example prompt: "Check my Gmail for new emails. Categorize as Urgent, Important, or FYI. Send me a Telegram summary. If anything is from my bank, doctor, or family, alert me immediately."

---

USE CASE 3: RESEARCH ON DEMAND Ask the agent to research any topic and return a structured summary.

Setup: Web browsing enabled Usage: On demand via Telegram Example: "Research the top 5 residential proxy services. Compare them on price, pool size, speed, and reliability. Return a comparison table."

---

USE CASE 4: CONTENT CONSUMPTION SUMMARY Monitor YouTube channels and websites, summarize new content weekly.

Setup: Web browsing, optional YouTube API Cron: Weekly Example: "Every Monday, check these 5 YouTube channels [list] for new videos posted this week. Give me a 2-sentence summary of each new video."

---

USE CASE 5: SUBSCRIPTION MONITORING Track subscription renewals, recurring charges, price changes.

Setup: Email (bank alerts forwarded to agent email) Cron: Weekly Example: "Each week, review bank notification emails and compile a list of upcoming subscription renewals in the next 30 days."

---

USE CASE 6: APPOINTMENT FOLLOW-UP TRACKING Track pending follow-ups and remind you before deadlines.

Setup: MEMORY.md + optional Google Calendar Usage: On demand to log; daily cron to review Example: "Remind me 2 days before any follow-up I've logged. If I haven't heard back from a contact in 10 days, remind me to check in."

SECTION 7: BUSINESS LIFE USE CASES (CFM BUSINESS)

USE CASE 1: UPWORK CANDIDATE SCREENING (Lisa — Active) The primary production use case for CFM.

What the agent does:

  1. Monitors Upwork inbox via browser (logged-in session)
  2. Reads new candidate messages
  3. Scores each candidate: Hot / Warm / Cold based on defined criteria
  4. Sends templated replies: intro (Hot), screening questions (Warm), polite pass (Cold)
  5. Logs all actions to MEMORY.md
  6. Sends Telegram summary to operator

Cron: Every 30–60 minutes during business hours Required: Browser infrastructure, Upwork session cookies

---

USE CASE 2: JOB POSTING MANAGEMENT Create, monitor, and optimize job postings.

What the agent does:

---

USE CASE 3: CANDIDATE FOLLOW-UP SEQUENCES Automated follow-up pipeline for candidates who don't respond.

Pipeline:

Setup: Cron checks MEMORY.md daily for follow-up triggers

---

USE CASE 4: DAILY OPERATIONS REPORT End-of-day automated summary of all agent activity.

Daily cron at end of business day. Report includes:

---

USE CASE 5: COMPETITOR MONITORING Weekly automated scan of competitor activity.

What the agent does:

---

USE CASE 6: CLIENT COMMUNICATION DRAFTING Draft professional correspondence.

Examples:

---

USE CASE 7: MARKET RATE RESEARCH Compile salary/rate data from multiple sources.

Weekly or monthly cron: "Pull current market rate data for [role] from 5 job boards. Compile into a comparison table. Email to me and save to /opt/data/reports/."

---

USE CASE 8: SOCIAL MEDIA CONTENT SCHEDULING Draft and queue social media posts.

Agent creates posts based on your guidelines (tone, topics, frequency) and either posts directly or queues for approval.

PART 4: OPERATIONS

SECTION 8: DASHBOARD GUIDE

Access: http://YOUR_VPS_IP:3000 in any web browser

KANBAN BOARD (Main View) Visual overview of all tasks as cards in columns:

Each card shows: task description, timestamp, session ID, duration, session type (cron vs. manual).

Click any card to open the full session transcript.

SESSION VIEWER Complete conversation logs for every session. Features:

CONNECTED PLATFORMS Real-time status of all integrations:

MEMORY VIEWER Read and edit USER.md and MEMORY.md directly in the browser. Changes save immediately to /opt/data/ — no terminal required. Use this to quickly update user preferences or add reminders.

CRON MANAGER

SKILLS BROWSER

SECTION 9: TELEGRAM INTERFACE & CONTEXT WINDOW MANAGEMENT

BASIC TELEGRAM USAGE:

CONVERSATION TYPES:

Both types behave identically from the agent's perspective.

CONTEXT WINDOW MANAGEMENT: Every AI model has a context window — the maximum text it can process at once. For claude-sonnet-4-6: 200,000 tokens (~150,000 words).

The Telegram interface provides LESS visibility into context usage than the CLI. In the terminal, you can monitor token counts directly. In Telegram, compaction may happen without obvious warning.

AUTO-COMPACTION AT ~136,000 TOKENS: When a Hermes conversation approaches 136,000 tokens, it automatically compacts (summarizes older portions) to allow the conversation to continue. What this means:

HOW TO AVOID PROBLEMS:

  1. Start fresh conversations for new topics (don't pile all work into one chat thread)
  2. Use memory files for important persistent info — they survive compaction
  3. Keep cron sessions focused and short — they don't benefit from long context chains
  4. If agent seems to be forgetting earlier details in the current session, start a new conversation

WHEN TO START A FRESH TELEGRAM SESSION:

SECTION 10: SCALING DECISION TREE & MAINTENANCE

DECISION: HOW MANY AGENTS AND WHAT HARDWARE?

One Agent (Current CFM State): Use when: One primary role, one main platform (Upwork) Hardware: KVM 2 ($10/mo) — handles 1–2 agents comfortably Model: MiniMax-M3 via the minimax provider

Add a Second Agent (Same VPS): Use when: Need a second distinct role (e.g., email manager + Upwork agent) Action: Add another Docker container with different --name and different /opt/data mount Hardware: Same KVM 2 if total RAM usage stays under 6GB

Upgrade VPS Hardware: Trigger: Slow response times, memory warnings, 2+ active agents Upgrade to: KVM 4 (~$20/mo): 4 vCPU, 16GB RAM Tip: Upgrade BEFORE problems are noticeable, not after

Separate VPS Per Critical Agent: Use when: One agent handles sensitive tasks (client comms, financial data) Benefit: Isolation means one agent's failure or compromise doesn't affect others Benefit: Easier troubleshooting when each server has one responsibility

COMMON MAINTENANCE COMMANDS:

Restart agent: docker restart hermes

View recent logs: docker logs hermes --tail 100

Follow live logs: docker logs hermes -f

Check container resource usage: docker stats hermes

Update Hermes to latest version: docker pull nousresearch/hermes:latest docker stop hermes docker rm hermes [re-run original docker run command — data persists in /opt/data/]

Check disk usage: df -h

Check memory usage: free -h

SECTION 11: SECURITY BEST PRACTICES

RULE 1 — ONE AGENT, ONE DEDICATED EMAIL Each Hermes agent must have its own Gmail account (or other email), separate from your personal email. Examples: lisaagent2026@gmail.com, hermescfm@gmail.com Why: If one account is phished or compromised, your personal email is unaffected. You can delete the agent's email without affecting your life.

RULE 2 — NAMED API KEYS, ONE PER AGENT On every API provider (MiniMax, GitHub, etc.), create separately named keys per agent. Example key name: "Lisa-CFM-Upwork-May2026" Why: (a) Track spending per agent in provider dashboards. (b) Revoke one key without disabling others. (c) Immediately identify which agent is causing unexpected API usage.

RULE 3 — LEAST PRIVILEGE Each agent gets only the minimum permissions required for its specific job.

Never grant an agent permissions "just in case" — only what it actively needs.

RULE 4 — SECRETS IN .ENV ONLY API keys, passwords, and tokens belong exclusively in .env files. Never store secrets in:

RULE 5 — PRIVATE GITHUB REPOSITORIES Agent backup repos must be PRIVATE. Backing up to a public repository exposes configuration, file paths, and potentially sensitive context.

RULE 6 — QUARTERLY CREDENTIAL ROTATION Every 3 months: review all agents' API keys, rotate any that are 6+ months old, check GitHub token expiry dates.

RULE 7 — VPS ACCESS HARDENING

RULE 8 — USE THE STANDARD MODEL The CFM standard is MiniMax-M3 via the minimax provider — it respects SOUL.md custom instructions. Do not swap in retired/safety-filtered models (GPT-class, etc.) that override behavioral rules. See PART 0 §0.2.

PART 5: PROBLEMS & SOLUTIONS

SECTION 12: 57 COMMON PROBLEMS — ROOT CAUSES AND SOLUTIONS ⚠️ HISTORICAL INCIDENT ARCHIVE — read accordingly (2026-06-23). These are DATED past incidents and the fixes applied at the time; they document how the system evolved, not the current config. Many reference now-obsolete models/providers (OpenRouter, Mistral, Claude Sonnet, Gemini, DeepSeek, gpt-4.1-mini) and Honcho memory. For the CURRENT standard ALWAYS defer to PART 0. Do NOT re-apply a model/provider/memory fix from this archive without checking PART 0 first.

QUICK INDEX — Find your problem by number:

[ SETUP & CONTAINER ]

  1. Docker container starts then immediately stops
  2. Telegram bot not responding
  3. Cron jobs not running
  4. Hermes dashboard not loading
  5. Container does not auto-restart after VPS reboot
  6. Cron not firing — timezone mismatch (UTC vs Central)
  7. Daily 4:00 AM UTC session reset wipes working context
  8. Container does not auto-restart after VPS reboot
  9. Disk space fills silently inside container
  10. config.yaml YAML syntax corruption crashes Hermes
  11. TELEGRAM_HOME_CHANNEL invalid — gateway crashes on every send
  12. SOUL.md deployed as broken symlink — agent runs with zero instructions

[ MODEL & LLM ]

  1. Agent ignores SOUL.md instructions
  2. Agent model provider not configured / silent fallback
  3. Gemini fallback causing rate limit crashes (429 errors)
  4. OpenRouter $20/month spending cap stops all API calls
  5. Documentation has wrong LLM model name
  6. Mistral model returns empty after multi-tool sequences
  7. GPT-4.1-mini refuses email operations via safety filters
  8. Anthropic API key hits monthly spending cap
  9. auth.json caches Anthropic key — model reverts to Claude on every restart

[ MEMORY & SESSIONS ]

  1. Agent loses memory between sessions
  2. Memory acting stale, confused, or contradictory

[ WEB SEARCH ]

  1. Agent says "I cannot access websites"
  2. DuckDuckGo rate limiting breaks web search

[ EMAIL ]

  1. IMAP email access failing
  2. GitHub backup failing
  3. Himalaya email binary corrupted — permanently bypassed
  4. Upwork email crons referencing broken himalaya
  5. Cron delivers 404 errors to Telegram instead of results
  6. Agent cherry-picks emails — silently skips important messages
  7. cfmbusiness OAuth token drifts or becomes invalid
  8. Local check_cfm_email.py out of sync with VPS version
  9. check_cfm_email.py has hardcoded Python version path
  10. Email scripts are read-only (RESOLVED – Option B implemented) — cannot reply to candidates

[ BROWSER & VNC ]

  1. Browser not working (blank or timeout)
  2. noVNC "Failed to connect to server"
  3. VNC tunnel URL changes after every VPS reboot
  4. Chrome cookie extraction returns all empty values
  5. Chrome crashes or hangs — no restart procedure
  6. Python virtualenv for browser components corrupted
  7. Upwork session cookie expires

[ UPWORK ]

  1. Upwork blocked by bot detection
  2. Facebook posting and Messenger completely blocked
  3. Upwork API (permanent solution) never pursued

[ GITHUB & BACKUPS ]

  1. GitHub backup failing

[ CREDENTIALS & SECURITY ]

  1. @anthropic-ai/claude-chrome-mcp NPM package does not exist
  2. Meta/Facebook page access tokens expire (~60 days)
  3. upwork_cookies.json on disk with 63 empty values — dead end
  4. setup_sonnet.py has full Anthropic API key hardcoded
  5. fix_model.py is a stale dangerous script

[ KNOWN GAPS — NOT YET RESOLVED ]

  1. Hermes browser skill never created
  2. One-time logins never completed (GHL, Facebook, QuickBooks)
  3. GoHighLevel integration status contradictory

[ DOCUMENTATION ]

  1. Three conflicting Google Doc IDs in local project files

---

EMERGENCY QUICK REFERENCE — Most Common Commands

Is the container running? docker ps | grep hermes docker start hermes-agent-jemg-hermes-agent-1

Read the last 30 log lines (find any crash cause): docker logs hermes-agent-jemg-hermes-agent-1 --tail 30

What model is actually running right now? docker exec hermes hermes config get MODEL_NAME

Restart Telegram gateway (bot not responding): docker exec hermes hermes telegram restart

Check memory file sizes (must stay within limits): docker exec hermes wc -c /opt/data/memories/USER.md /opt/data/memories/MEMORY.md Limits: USER.md ≤1,375 chars | MEMORY.md ≤2,200 chars

Full container restart (reloads all config, memory, crons): docker restart hermes-agent-jemg-hermes-agent-1

List all crons and their status: docker exec hermes hermes cron list

Check disk space inside container: docker exec hermes df -h

---

ABSOLUTE RULES — NEVER DO THESE THINGS

NEVER use sed to edit a Python file on the VPS. sed strips Python string quotes and silently corrupts the file. The error may not appear until a restart. Only safe method: edit locally at Lisa_Hermes/scripts/ → deploy via scp + docker cp.

NEVER create a new Google Doc to update the master manual. Canonical Doc ID: 1MMM5zurb9PFFDhCPKaQ_SRJOOKvTI0hGMnkKYlguNmY Edit that document directly. Every new doc creates a version that will mislead future agents.

NEVER use mistralai/mistral-small as the model ID. That endpoint does not exist on OpenRouter and returns HTTP 404. Correct ID: mistralai/mistral-small-3.2-24b-instruct

NEVER install @anthropic-ai/claude-chrome-mcp. This npm package does not exist. Always fails. Use chrome-devtools-mcp@latest instead.

NEVER use fine-grained GitHub tokens for the backup cron. Fine-grained tokens cause authentication failures reliably. Use classic tokens with repo scope only.

NEVER reinstall or reference himalaya. The binary was corrupted from day one (9-byte "Not Found" file). It has never worked. Use check_upwork_email.py and check_cfm_email.py instead. They bypass himalaya entirely.

NEVER trust or use upwork_cookies.json. All 63 cookie values are empty strings (decryption failed silently). This file is useless. Delete it: docker exec hermes rm -f /opt/data/upwork_cookies.json

PROBLEM #1 — UPWORK LOGIN EXPIRED (Most Common Issue — Solved May 17, 2026)

PLAIN ENGLISH — WHAT DARRICK NEEDS TO DO (3 minutes):

  1. Lisa will send you a link in Telegram. Click it.

It opens a remote browser that runs on the server — looks like a normal browser.

  1. In that browser, go to upwork.com and log in with your normal Upwork

username and password, just like you always do.

  1. Once you can see your Upwork messages, text Lisa back one word: done
  1. Lisa takes care of everything else automatically and confirms when it works.

That is it. You do not need to run any scripts or do anything technical. This same 3-step process works for ANY website that loses its login, not just Upwork. The login lasts 30-90 days before needing to repeat.

--- TECHNICAL DETAILS (for Claude Code / developer reference only) ---

SYMPTOMS:

ROOT CAUSE: Chrome stores authenticated sessions as encrypted cookies in a SQLite database file at: /opt/data/browser-manager/profiles/default/Default/Cookies (inside container) These cookies expire every 30-90 days. CDP cookie injection (Network.setCookie) is memory-only and does NOT survive Chrome restart — this is why it was never a real fix.

THE FIX — COOKIE FILE SWAP (5 minutes total): Both Chrome instances on the VPS (VNC browser + Lisa's headless Chrome) use IDENTICAL Linux cookie encryption: AES-128-CBC, key derived from password "peanuts". This means a Cookies file from one Chrome is byte-for-byte compatible with the other.

Step 1 — Log in via VNC:

Step 2 — Darrick logs into the remote desktop link Lisa sends, logs into Upwork, says "done" Lisa then runs: bash /opt/data/scripts/restore_browser_session.sh (inside container) (Windows fallback if needed: cd "C:\Users\12149\Documents\Hermes Agents" then python swap_cookies.py)

What the script does automatically:

  1. Disables the browser watchdog cron temporarily
  2. Kills Lisa's Chrome (releases the file lock on Cookies)
  3. Backs up Lisa's Cookies to Cookies.backup
  4. Copies VNC Chrome's Cookies to Lisa's Chrome profile
  5. Restarts Chrome with all original flags (headless, port 9222, proxy, etc.)
  6. Re-enables the browser watchdog
  7. Navigates to Upwork to verify session is active

Step 3 — Verify: Script output should show: LOGGED_IN:https://www.upwork.com/ab/messages/rooms

HOW LONG IT LASTS: 30-90 days. Repeat when session expires again.

WORKS FOR ANY WEBSITE — not just Upwork: Log in to any site via VNC Chrome, then run swap_cookies.py. The script copies ALL site cookies at once.

LISA'S SELF-DIAGNOSIS: When Lisa detects she cannot reach an authenticated page, she should:

  1. Check if URL redirected to /login or /account-security
  2. NOT attempt to log in herself (cannot securely enter credentials)
  3. Send Darrick this message:

"My Upwork session has expired. Please log in through VNC and run swap_cookies.py from your Windows machine."

FILES: Recovery script: C:\Users\12149\Documents\Hermes Agents\swap_cookies.py Full protocol doc: C:\Users\12149\Documents\Hermes Agents\COOKIE_SWAP_PROTOCOL.md Lisa's memory file: /opt/data/memories/BROWSER_SESSION_RECOVERY.md (inside container)

WHY CDP INJECTION FAILS: Network.setCookie via CDP writes to Chrome's in-memory cookie store only. On Chrome restart or when CDP disconnects, these cookies are gone. The SQLite file swap writes to disk — persistent across all reboots indefinitely.

PROBLEM 1: DOCKER CONTAINER STARTS THEN IMMEDIATELY STOPS

Symptom: docker ps shows hermes with status "Exited (1)"

Root cause: Missing environment variable, port conflict, or permission error

Solution:

  1. Read the error: docker logs hermes
  2. If "port already in use" → change -p 3000:3000 to -p 3001:3000 and update your bookmark
  3. If "API key not found" → add the required key: docker exec hermes hermes config set KEY "value"
  4. If "permission denied on /opt/data" → run: chmod 755 /opt/data
  5. If no obvious error → try: docker rm hermes then re-run the original docker run command

---

PROBLEM 2: TELEGRAM BOT NOT RESPONDING

Symptom: Messages to bot receive no reply

Diagnostic steps (in order):

  1. Check container is running: docker ps
  2. Check Telegram integration: docker exec hermes hermes telegram status
  3. Verify token is correct: docker exec hermes hermes config get TELEGRAM_BOT_TOKEN
  4. Restart Telegram: docker exec hermes hermes telegram restart
  5. Check logs: docker logs hermes --tail 50
  6. Try restarting the full container: docker restart hermes

[SCREENSHOT: Terminal showing hermes telegram status command output]

---

PROBLEM 3: AGENT SAYS "I CANNOT ACCESS WEBSITES"

Symptom: Agent refuses to browse web, says tools are unavailable

Two distinct causes with different solutions:

Cause A — Web SEARCH unavailable (no browser needed): Fix: Add search API key docker exec -it hermes hermes config set SEARCH_API_KEY "your-key" Provider options: SerpAPI, Brave Search, DuckDuckGo (free but rate-limited)

Cause B — Web BROWSING unavailable (browser infrastructure needed): Fix: Complete Phase 11 (Browser Infrastructure Setup) in this manual Signs this is the cause: Search works, but navigating to specific pages fails

Cause C — search_backend not configured in config.yaml (most common root cause): Root cause: The search_backend key in /opt/data/config.yaml is empty ('') by default. DuckDuckGo is already installed — it just has never been activated. Fix:

  1. Check current setting: docker exec hermes grep search_backend /opt/data/config.yaml

If it shows search_backend: '' (empty) — this is the cause.

  1. Enter the container: docker exec -it hermes bash
  2. Edit config: nano /opt/data/config.yaml

Change: search_backend: '' to search_backend: 'ddgs' Save and exit.

  1. Restart: docker restart hermes

Silent fallback warning: If the primary model fails and no valid fallback is configured, Hermes may silently fall back to a default model (such as deepseek-chat) with no error message. The agent continues responding but with worse results. To confirm which model is actually active: docker exec hermes hermes config get MODEL_NAME or: docker exec hermes hermes config list

Model ID must be exact — common mistakes:

To verify a model ID exists on OpenRouter before setting it: curl https://openrouter.ai/api/v1/models -H "Authorization: Bearer YOUR_KEY" | python3 -m json.tool | grep '"id"' | grep mistral

  1. Verify: Ask the agent to search the web — she should now return results.

No API key required. DuckDuckGo (ddgs) is free and built in.

---

PROBLEM 4: AGENT LOSES MEMORY BETWEEN SESSIONS

Symptom: Agent doesn't remember previous conversations, asks for info already given

Root cause: Memory files missing or /opt/data not properly mounted

Solution:

  1. Check if files exist:

docker exec hermes ls /opt/data/

  1. If USER.md or MEMORY.md missing: Create them (Phase 9 in this manual)
  2. Verify volume mount: docker inspect hermes | grep Mounts

Should show /opt/data mapped to /opt/data

  1. If mount is wrong: Stop container, remove, recreate with -v /opt/data:/opt/data flag

Character limits for memory files: USER.md must stay at or under 1,375 characters. MEMORY.md must stay at or under 2,200 characters. Exceeding these limits can prevent Hermes from loading them into context at session start. Periodically check sizes with: docker exec hermes wc -c /opt/data/memories/USER.md /opt/data/memories/MEMORY.md If over limit, ask Lisa: "Read me your MEMORY.md" — identify outdated entries and remove them.

---

PROBLEM 5: AGENT IGNORES SOUL.MD INSTRUCTIONS

Symptom: Agent doesn't follow custom personality rules, ignores behavioral guidelines

Root cause: Model safety filters overriding custom instructions (GPT-4.1-mini is the primary culprit)

Solution: Switch to a model that respects custom instructions: docker exec -it hermes hermes config set MODEL_NAME "anthropic/claude-sonnet-4-6"

Budget alternative: mistral/mistral-small

---

PROBLEM 6: CRON JOBS NOT RUNNING

Symptom: Scheduled tasks never execute

Diagnostic steps:

  1. List crons: docker exec hermes hermes cron list

Verify the cron exists and is enabled

  1. Check timezone: All times in UTC. Calculate: your local time + UTC offset = UTC time

Example: 9am CDT = 2pm UTC = cron schedule "0 14 *"

  1. Test manually: docker exec hermes hermes cron run [cron-id]
  2. Review session history in dashboard for any error output
  3. Check: Was the cron created inside another cron session? (Not allowed — must create from manual session)

---

PROBLEM 7: BROWSER NOT WORKING (BLANK OR TIMEOUT)

Symptom: Browser tasks fail, screenshots return blank or never return

Diagnostic steps:

  1. Check Chrome is running: pgrep chrome

If empty (no process): restart Chrome (see commands below)

  1. Check CDP responds: curl http://localhost:9222/json

If error: Chrome CDP is not active

  1. Check Xvfb is running: pgrep Xvfb

If empty: restart Xvfb

Restart Chrome: pkill chrome DISPLAY=:99 google-chrome --headless --disable-gpu --remote-debugging-port=9222 --no-sandbox --disable-dev-shm-usage &

Restart Xvfb: Xvfb :99 -screen 0 1920x1080x24 &

---

PROBLEM 8: UPWORK BLOCKED BY BOT DETECTION

Symptom: Browser reaches Cloudflare challenge page or "Access Denied" on Upwork

Root cause: Upwork's Cloudflare + Forter stack blocks the VPS's datacenter IP, or the saved session cookie has expired.

Solution: Option A — Manual login via VNC tunnel: Darrick logs in by hand via noVNC, session cookie saved, valid 30–90 days (see Problems 15 and 16) Option B — Upwork OAuth API (permanent fix): no browser needed; create an OAuth app at developers.upwork.com, obtain Key + Secret, wire into the agent — works from any IP forever

Then restart the browser.

See also: Problem 15 (noVNC connection failure), Problem 16 (VNC tunnel URL changes), Problem 39 (Upwork session cookie expires), Problem 50 (Upwork OAuth API — permanent fix).

---

PROBLEM 9: IMAP EMAIL ACCESS FAILING

Symptom: Agent cannot read emails, authentication errors

Gmail with App Password (for hermesCFM account):

Gmail REST API (OAuth) (for cfmbusiness account):

---

PROBLEM 10: HERMES DASHBOARD NOT LOADING

Symptom: http://VPS_IP:3000 gives "connection refused" or just times out

Diagnostic steps:

  1. Confirm container is running: docker ps
  2. Check what's on port 3000: netstat -tlnp | grep 3000
  3. Check firewall status: ufw status
  4. If UFW is blocking: ufw allow 3000
  5. Restart container: docker restart hermes
  6. If still failing: docker logs hermes --tail 20 for errors

Port changes on every restart — pin it permanently: If using docker-compose.yml and the dashboard URL breaks after every container restart because Docker assigns a random host port, pin it explicitly: In docker-compose.yml, set: ports: ['4860:4860'] This locks the host port to 4860 permanently regardless of restarts. Update the firewall: ufw allow 4860 Dashboard URL will then always be: http://VPS_IP:4860

---

PROBLEM 11: AGENT MODEL PROVIDER NOT CONFIGURED

Symptom: Agent responds with "I don't have an LLM configured" or uses wrong model, or replies make no sense

Solution:

  1. Check current config: docker exec hermes hermes config list
  2. Set API key: docker exec hermes hermes config set OPENROUTER_API_KEY "sk-or-your-key"
  3. Set model: docker exec hermes hermes config set MODEL_NAME "anthropic/claude-sonnet-4-6"
  4. Restart: docker restart hermes

Silent fallback warning: If the primary model fails and no valid fallback is configured, Hermes may silently fall back to a default model (such as deepseek-chat) with no error message. The agent continues responding but with worse results. To confirm which model is actually active: docker exec hermes hermes config get MODEL_NAME or: docker exec hermes hermes config list

Model ID must be exact — common mistakes:

To verify a model ID exists on OpenRouter before setting it: curl https://openrouter.ai/api/v1/models -H "Authorization: Bearer YOUR_KEY" | python3 -m json.tool | grep '"id"' | grep mistral

---

PROBLEM 12: GITHUB BACKUP FAILING

Symptom: Backup errors, "authentication failed", "repository not found", or 401 errors

Solution:

  1. VERIFY TOKEN TYPE: Must be CLASSIC personal access token, not fine-grained.

Fine-grained tokens cause this error reliably.

  1. Check token hasn't expired: GitHub → Settings → Developer Settings → Personal Access Tokens
  2. Verify repo format: Must be "username/repo-name" (exactly, no URL prefix)
  3. Replace token:

docker exec -it hermes hermes config set GITHUB_TOKEN "ghp_your-new-token"

  1. Test:

docker exec -it hermes hermes backup now

---

Script path issue — "Script not found: /opt/data/scripts/github-sync.sh": The github-sync.sh script may exist at /opt/data/github-sync.sh or /opt/data/.hermes/scripts/github-sync.sh but the cron runner expects it at /opt/data/scripts/github-sync.sh. Fix: docker exec hermes cp /opt/data/github-sync.sh /opt/data/scripts/github-sync.sh Verify: docker exec hermes ls /opt/data/scripts/github-sync.sh

---

PROBLEM 66: MISTRAL SMALL 3.2 RETURNS EMPTY RESPONSES ON TOOL CALLS — OPENROUTER CACHES THE EMPTY REPLY

Symptom: Lisa responds to every Telegram message with "I cannot do that" or a generic refusal regardless of the request. Gateway logs show the pattern: Empty response (no content or reasoning) — retry 1/3, retry 2/3, retry 3/3, followed by OpenRouter response cache HIT (total: 1), (total: 2), (total: 3), then Empty response after 3 retries. No fallback available. Messages are received and acknowledged but every response is a refusal.

Root cause: Mistral Small 3.2's RLHF safety training returns a completely empty response body for tool-calling scenarios, even when the system prompt explicitly authorizes the task. OpenRouter caches this empty response at the infrastructure level. Hermes retries the request three times — but all three retries hit the OpenRouter cache and receive the same cached empty reply. Hermes exhausts its retry budget and sends a hardcoded fallback message ("I cannot do that") to Telegram. The SOUL.md instructions are never reached because the model returns nothing before they can take effect.

Solution:

  1. Switch primary model to anthropic/claude-sonnet-4-6 via OpenRouter:

docker exec hermes-agent-jemg-hermes-agent-1 bash -c 'hermes config set model.default anthropic/claude-sonnet-4-6 && hermes config set model.provider openrouter' IMPORTANT: routing through OpenRouter bypasses all direct Anthropic API spending caps — these are completely separate billing systems. The Anthropic key in /opt/data/.env is irrelevant when provider is set to openrouter.

  1. Restart gateway inside the container (NOT docker restart — that resets config to defaults):

docker exec -u hermes hermes-agent-jemg-hermes-agent-1 bash -c 'pkill -f "hermes gateway run" 2>/dev/null; sleep 3; nohup hermes gateway run > /tmp/gateway.log 2>&1 &'

  1. Verify via gateway log: confirm response time is greater than 0s and response character count is greater than 0.

Do NOT use: Mistral Small 3.2 as the primary model for any Hermes agent. The empty response bug is caused by its RLHF training and cannot be fixed with prompting, system prompt changes, or retry configuration adjustments. GPT-4.1-mini is a functional fallback but is not the official Nous Research recommendation — use Claude Sonnet 4.6 via OpenRouter as the primary model for all Hermes agents.

Status: FIXED — May 16, 2026. Switched to anthropic/claude-sonnet-4-6 via OpenRouter. Routing through OpenRouter bypasses all direct Anthropic API spending caps — these are separate billing systems. Gateway log confirmed: response ready: time=21.6s api_calls=2 response=393 chars.

See also: Problem 57 (model reverts to Claude on restart), Problem 53 (GPT-4.1-mini refuses email via safety filters).

---

PROBLEM 67: FACEBOOK SOUL.MD TRIGGER MAP AMBIGUITY — "LOGIN TO FACEBOOK" ATTEMPTS BROWSER AUTOMATION INSTEAD OF ASKING WHICH ACCOUNT

Symptom: When told "login to my Facebook" or "check my Facebook," Lisa attempts browser automation to open facebook.com, which fails on VPS servers because Facebook detects and blocks server IP ranges. Lisa then reports she cannot log in rather than asking a clarifying question. No Facebook task is completed.

Root cause: SOUL.md contained a single ambiguous Facebook entry routing all requests to browser automation: "login to Facebook → navigate browser to https://www.facebook.com." This forced browser automation for every Facebook request — ignoring that the user operates two completely different Facebook contexts: (1) Business Pages (USA Gig Work, Fix Flint) accessible via Meta Graph API with no browser required; (2) Personal profile, accessible only via VNC browser tunnel because Facebook blocks direct server automation. The single entry also meant Lisa never asked which account — she always defaulted to browser and failed.

Solution: Replace the single Facebook entry in SOUL.md with three distinct entries in the tool trigger map:

Deploy facebook_manager.py to /opt/data/scripts/facebook_manager.py — a Meta Graph API script handling posts, comments, comment posting, and replies with no browser required.

Critical bug in facebook_manager.py to verify: The token_for_post() function must parse the page ID from the post ID prefix (e.g., 447725185097879_POSTID belongs to USA Gig Work page). Without this fix, the script defaults to the alphabetically-first page token (Fix Flint), which returns permission errors on USA Gig Work content.

Status: FIXED — May 16, 2026. SOUL.md updated with three-entry disambiguation. facebook_manager.py deployed at /opt/data/scripts/facebook_manager.py with correct token routing logic.

See also: Problem 8 (Upwork bot detection and proxy configuration), Problem 46 (proxy_agent.py does not read from .env).

---

PROBLEM 68: PLAYWRIGHT CHROMIUM CRASHES IMMEDIATELY IN CONTAINER — XVFB ZOMBIE PROCESSES

Symptom: Every browser-dependent task fails with "Failed to resolve CDP endpoint http://localhost:9222." Running ps aux inside the container shows many [chrome] <defunct> zombie processes. Chrome starts and immediately dies — no usable CDP connection is ever established. Restarting browser_manager.py repeatedly produces the same zombie result. Crash dumps accumulate at /opt/data/home/.config/google-chrome-for-testing/Crash Reports/pending/.

Root cause: Playwright Chromium (bundled at /opt/data/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome) requires an X11 virtual display (Xvfb) to launch in non-headless mode. Xvfb crashes on container startup because the container kernel does not support the required X11 socket infrastructure. This creates a cascade failure: Xvfb fails → Chrome has no display → Chrome exits immediately → zombie processes accumulate. The launch flags --no-sandbox and --disable-dev-shm-usage do not fix this because the failure is at the display layer, not the sandbox or shared-memory layer.

Solution: Switch Chrome to headless mode — no virtual display required:

  1. Update /opt/data/browser-manager/browser_manager.py — remove DISPLAY=:99 from the environment, add --headless=new to the Chrome launch arguments.
  2. Update /opt/data/browser-watchdog.sh — remove all Xvfb start logic. Use --headless=new in the Chrome restart command.
  3. Kill all zombie processes and clean up display locks:

pkill -9 -f chrome 2>/dev/null; pkill -f Xvfb 2>/dev/null; rm -f /tmp/.X99-lock /tmp/.X98-lock; sleep 2

  1. Restart browser_manager:

nohup python3 /opt/data/browser-manager/browser_manager.py > /tmp/browser_manager.log 2>&1 &

  1. Verify: curl -s http://localhost:9222/json/version should return JSON containing "Browser": "Chrome/147..."

Note: --headless=new supports full CDP functionality including page navigation, screenshots, form filling, and JavaScript execution. Visual rendering is the only capability lost, which is irrelevant for server-side automation.

Status: FIXED — May 16, 2026. Chrome/147 running in headless=new mode. CDP responding on port 9222. Browser watchdog updated and redeployed to host crontab.

See also: Problem 53 (browser_manager.py crashes on restart), Problem 54 (Chrome crashes under heavy page load).

---

PROBLEM 69: HERMES WATCHDOG KILLS GATEWAY TO FIX MODEL CONFIG BUT NEVER RESTARTS IT — LISA GOES SILENT AFTER EVERY WATCHDOG INTERVENTION

Symptom: After the watchdog runs (every 5 minutes), Lisa stops responding on Telegram entirely. No gateway process is found running inside the container. The watchdog log shows it detected the wrong model, killed the gateway, and corrected config.yaml — but Lisa never came back online. The watchdog "fixed" the agent by silencing her indefinitely until a human manually restarted the gateway.

Root cause: The original hermes_watchdog.py logic was: (1) read config.yaml, (2) if model is wrong, fix config.yaml, (3) pkill -f "hermes gateway run" — then stop. No start_gateway() call followed the kill. The script assumed the gateway would auto-restart itself after being killed — it does not. Every watchdog fix left Lisa in a dead, non-responding state. Additionally, the watchdog had no independent check for whether the gateway was running at all — if the gateway died for reasons other than model config, the watchdog would not notice.

Solution: Rewrite /opt/hermes_watchdog.py with two additions: a start_gateway() function and an independent gateway_running() health check:

def start_gateway(): subprocess.Popen( ['docker', 'exec', '-u', 'hermes', CONTAINER, 'bash', '-c', 'nohup hermes gateway run > /tmp/gateway.log 2>&1 &'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) time.sleep(5)

def gateway_running(): result = subprocess.run( ['docker', 'exec', CONTAINER, 'pgrep', '-f', 'hermes gateway run'], capture_output=True ) return result.returncode == 0

Updated logic flow: (1) check model config → if wrong, fix config → kill gateway → call start_gateway(); (2) independently call gateway_running() → if gateway is down for any reason, call start_gateway() — even when the config is already correct.

Status: FIXED — May 16, 2026. Watchdog rewritten and redeployed to /opt/hermes_watchdog.py on host. Watchdog enforces anthropic/claude-sonnet-4-6 via openrouter as the correct model — any reversion to a different model is auto-corrected within 5 minutes. Both the model-fix path and the independent gateway health check now call start_gateway() after any intervention. Host cron confirmed active: /5 * /usr/bin/python3 /opt/hermes_watchdog.py.

See also: Problem 57 (auth.json caches Anthropic key — model reverts to Claude on restart).

APPENDIX: GLOSSARY OF TERMS

AGENTS.MD — A local context file (like CLAUDE.md) placed in a project directory for CLI-mode Hermes sessions. The agent reads it automatically when running in that directory.

AUTO-COMPACTION — When a Hermes conversation approaches ~136,000 tokens, older portions are automatically summarized to allow the conversation to continue. All sessions remain fully searchable in SQLite regardless.

CDP (Chrome DevTools Protocol) — A protocol that allows programs to control a Chrome browser: clicking buttons, filling forms, taking screenshots, reading page content. How Hermes autonomously operates web browsers.

CLASSIC TOKEN (GitHub) — A GitHub personal access token with broad scope settings. Required for Hermes GitHub backup. Use this, not fine-grained tokens.

CLOUDFLARE / FORTER — Bot detection systems used by websites like Upwork. Identifies and blocks traffic from datacenter IPs. Worked around with a real logged-in browser session or the site's official API.

CONTEXT WINDOW — The maximum amount of text an AI model can process in one session. For claude-sonnet-4-6: 200,000 tokens (~150,000 words).

CRON — A scheduled, automated task. Runs at set times without any human input. Name comes from the Unix cron job scheduler.

DOCKER — Containerization platform. Hermes runs inside a Docker container — an isolated, portable environment with everything it needs. Containers can be stopped, restarted, and recreated without losing data in mounted volumes.

FINE-GRAINED TOKEN (GitHub) — A newer GitHub access token type with per-repository permissions. NOT compatible with Hermes backup — use Classic tokens instead.

HERMES — Open-source autonomous AI agent framework by Nous Research. 140,000+ GitHub stars, MIT licensed. The framework for the agent; the LLM provides the intelligence.

IMAP — Internet Message Access Protocol. Standard protocol for accessing email from a mail server. Used to connect Gmail to Hermes for email reading.

KANBAN BOARD — Visual task management: cards organized in columns (Pending, In Progress, Complete, Failed). Available in the Hermes dashboard.

LEAST PRIVILEGE — Security principle: grant only the minimum permissions required for a task. Each agent should have only the access it actually needs.

LLM (Large Language Model) — The AI model powering Hermes's reasoning (the CFM standard is MiniMax-M3; other providers exist but are not used here). Hermes is the agent framework; the LLM is the "brain."

MEMORY.MD — Working memory file. Contains evolving, frequently-updated information. Located at /opt/data/MEMORY.md.

OPENROUTER — API aggregator providing access to 100+ AI models via a single API key. CFM standard LLM provider.

PROGRESSIVE DISCLOSURE — Hermes reads only YAML metadata from skill files at startup (not the full content). Full skill content loads only when the skill is triggered. Keeps context window efficient with many skills installed.

RESIDENTIAL PROXY — Proxy that routes traffic through real home internet connections. Makes VPS traffic appear as a regular home user. Not used in this stack.

SESSION — A single conversation with the Hermes agent. All sessions stored in SQLite at /opt/data/sessions.db. Fully searchable.

SKILL — A markdown file with YAML front matter + step-by-step instructions for a specific task type. Stored in ~/.hermes/skills/.

SOUL.MD — The agent's identity, personality, and behavioral rules file. Located at /opt/data/SOUL.md.

SQLITE — Lightweight file-based database. Hermes stores all session transcripts in /opt/data/sessions.db. Queryable by the agent itself.

SSH — Secure Shell. Protocol for securely connecting to a remote server via terminal.

TERMUX — Android terminal emulator. Allows Hermes to run on Android phones. For personal experimentation only — not recommended for production.

TOKEN (API) — Unit of text processed by AI models. Roughly 3/4 of a word. API usage is priced per token.

USER.MD — Permanent memory file. Contains stable facts about the user. Located at /opt/data/USER.md. Read at the start of every conversation.

VPS (Virtual Private Server) — A rented server in a data center that runs 24/7. Hermes agents run on VPS instances so they operate continuously without your computer being on.

XVFB — X Virtual Framebuffer. Software that simulates a monitor. Chrome needs a display to render to — Xvfb provides a virtual one on a headless Linux server.

YAML FRONT MATTER — Metadata at the top of a markdown file, between --- markers. Used in skill files to provide structured metadata (name, trigger, description) the agent reads without loading the full file.

END OF MANUAL

Hermes Agent Master Manual — CFM Business Version 4.0 FINAL | May 2026 Maintained by: CFM Business Update rule: Edit this document directly. NEVER create a new Google Doc for updates. For new problems/solutions: Add to Section 12 above.

---

PROBLEM 14: GEMINI FALLBACK CAUSING RATE LIMIT CRASHES

Symptom: Flood of 429 "Too Many Requests" errors; agent becomes unresponsive or loops endlessly

Root cause: A Gemini model was configured as a fallback. The Gemini free tier quota was exhausted (0 remaining). Every time the primary model had any issue, Hermes called Gemini, which immediately returned 429, creating a crash loop.

Solution:

  1. Remove the Gemini fallback entirely from config:

docker exec -it hermes hermes config set FALLBACK_MODEL ""

  1. Set both primary and fallback to OpenRouter models with confirmed quota:

docker exec -it hermes hermes config set MODEL_NAME "mistralai/mistral-small-3.2-24b-instruct"

  1. Restart: docker restart hermes
  2. Rule: Never use free-tier model quotas as fallbacks — they hit zero silently and trigger crash loops with no clear error.

---

PROBLEM 15: NOVNC "FAILED TO CONNECT TO SERVER" — BROWSER REMOTE ACCESS BROKEN

Symptom: Opening the VNC tunnel URL and clicking Connect shows a red error banner. Cannot see or control the agent's browser.

Root cause: Two components must run simultaneously and point to each other correctly:

If Xvfb is a zombie process (started but crashed), x11vnc cannot attach to it and the chain breaks.

Solution:

  1. Check if Xvfb is alive: pgrep Xvfb
  2. If dead or zombie, restart the full container first — browser_manager will reinitialize Xvfb on startup:

docker restart hermes

  1. After restart, start x11vnc inside the container:

docker exec -d hermes x11vnc -display :99 -nopw -listen localhost -xkb -forever

  1. Find the container's internal IP: docker inspect hermes | grep '"IPAddress"'
  2. Start websockify on the host pointing at that IP:

websockify 6080 172.16.1.2:5900 & (Replace 172.16.1.2 with the actual container IP from step 4)

  1. Open the noVNC URL — it should now connect.

---

PROBLEM 16: VNC TUNNEL URL CHANGES AFTER EVERY VPS REBOOT

Symptom: After any VPS reboot or cloudflared restart, the tunnel URL (e.g. https://example.trycloudflare.com) stops working. A new random URL is generated each time and must be hunted down.

Root cause: cloudflared in "quick tunnel" mode generates a temporary random subdomain that expires when the process restarts. There is no fixed hostname.

Temporary fix — find the current URL after a reboot: grep -oa "https://[a-z0-9-]*\.trycloudflare\.com" /tmp/cloudflared.log | tail -1

Permanent fix — named Cloudflare tunnel with fixed subdomain (requires Cloudflare account):

  1. Install cloudflared on host VPS
  2. Authenticate: cloudflared tunnel login
  3. Create named tunnel: cloudflared tunnel create hermes-vnc
  4. Configure DNS CNAME: vnc.yourdomain.com → [tunnel-id].cfargotunnel.com
  5. The URL will then be stable across reboots.

---

PROBLEM 17: HIMALAYA EMAIL BINARY CORRUPTED — ALL EMAIL ACCESS BROKEN

Symptom: Every email task fails with "himalaya is not available," executable errors, or returns the text "Not Found."

Root cause: The himalaya binary at /opt/data/.local/bin/himalaya was never properly installed. A failed HTTP download during setup saved a 9-byte error page ("Not Found") as the executable. It was never functional at any point.

Solution: Himalaya is permanently bypassed. Do NOT reinstall or attempt to fix it. Two Python scripts replace it entirely:

check_upwork_email.py — reads Upwork email via Python imaplib + Gmail app password: Location: /opt/data/browser-manager/tasks/check_upwork_email.py Usage: python3 /opt/data/browser-manager/tasks/check_upwork_email.py

check_cfm_email.py — reads cfmbusiness Gmail via Gmail REST API + OAuth2 token: Location: /opt/data/browser-manager/tasks/check_cfm_email.py Usage: python3 /opt/data/browser-manager/tasks/check_cfm_email.py N (Replace N with the number of emails to retrieve — e.g., 5 for "check my last 5 emails")

PERMANENT RULE: Do not use, reference, or reinstall himalaya. Both Python scripts work without it and are more reliable.

See also: Problem 18 (crons still referencing himalaya), Problem 19 (cron 404 Telegram errors), Problem 20 (email cherry-picking fix), Problem 51 (email scripts are read-only).

---

PROBLEM 18: UPWORK EMAIL CRONS REFERENCING BROKEN HIMALAYA — FIRING BUT FAILING

Symptom: Upwork email crons fire on schedule but produce errors every run. Cron history shows himalaya-related failures.

Root cause: The cron entries in /opt/data/cron/jobs.json still referenced "skill": "himalaya" even after himalaya was bypassed and replaced with Python scripts. The cron system tried to invoke the broken skill on every scheduled run.

Solution:

  1. Access the cron config: docker exec -it hermes bash

Then: nano /opt/data/cron/jobs.json

  1. Find Upwork email cron entries (names like "Upwork Email Monitor," "upwork-reply-monitor")
  2. Remove "skill": "himalaya" from those entries
  3. Change the prompt to invoke the Python script directly and output as response:

"prompt": "Run python3 /opt/data/browser-manager/tasks/check_upwork_email.py and output the results as your response."

  1. Save the file and restart: docker restart hermes
  2. Verify: docker exec hermes hermes cron list — confirm no himalaya reference remains

---

PROBLEM 19: CRON DELIVERS 404 ERRORS TO TELEGRAM INSTEAD OF RESULTS

Symptom: Cron fires on schedule. Darrick receives 404 error messages on Telegram instead of the actual email results. The agent is attempting to call the Telegram Bot API directly.

Root cause: The cron prompt instructed Lisa to "forward results to this Telegram chat." She interpreted this as a direct Telegram API call she had to make herself. Those API calls require authentication she doesn't have in scope, so every attempt returns 404.

The Hermes cron system handles Telegram delivery automatically — the agent should only output the result as a response. It does not need to call Telegram directly.

Solution: Change the cron prompt from "forward to Telegram" to "output as your response."

Correct cron prompt format: "Run python3 /opt/data/browser-manager/tasks/check_upwork_email.py and output the results as your response."

Incorrect (causes 404 loop): "Run the email check and forward results to Telegram chat ID 870599219."

---

PROBLEM 20: AGENT CHERRY-PICKS EMAILS — SILENTLY SKIPS IMPORTANT MESSAGES

Symptom: When asked "check my last 5 emails," the agent shows 5 emails — but not the actual 5 most recent. Real estate deals and other important messages are silently skipped. She appears to judge which emails are "worth" showing.

Root cause: The email script returned all emails as unstructured text. The LLM chose which lines from the output to include in its reply. This is LLM reasoning behavior and cannot be overridden by SOUL.md instructions alone.

Failed fix: Adding strict rules to SOUL.md ("never filter, always show all"). This did NOT work. The LLM continued to filter silently.

Actual fix: Rewrote check_cfm_email.py so that:

  1. It accepts a count argument: python3 check_cfm_email.py N
  2. It retrieves exactly N emails from Gmail API
  3. It pre-formats and numbers each one: --- Email 1 of N --- / --- Email 2 of N ---
  4. It returns them in order with no selection opportunity

With this structure, the agent receives pre-numbered emails and copies them into her response. The count is controlled by the script — the LLM has no opportunity to filter.

SOUL.md and MEMORY.md must also be updated to tell the agent to pass the count argument: "When the user asks to check emails, run: python3 check_cfm_email.py [N] where N is the number they requested."

See also: Problem 17 (himalaya bypassed — the Python scripts that replaced it), Problem 37 (local check_cfm_email.py out of sync with VPS), Problem 51 (email scripts are read-only — cannot yet reply to candidates).

Verified: After the rewrite, "check my last 5 CFM Business emails" returned exactly 5 emails in order, including real estate deals that had previously been silently skipped.

---

PROBLEM 21: CONTAINER CANNOT RESOLVE EXTERNAL HOSTNAMES (DNS)

Symptom: curl inside the container fails with a DNS resolution error on an external hostname, even though the host resolves it fine.

Root cause: Docker containers inherit DNS from the host, but some Docker network configurations do not forward external DNS lookups correctly.

Fix: echo 'nameserver 8.8.8.8' >> /etc/resolv.conf (inside the container) Or add DNS settings to docker-compose.yml: dns: ["8.8.8.8", "8.8.4.4"] After applying, test: docker exec hermes nslookup example.com

---

PROBLEM 22: HERMES GATEWAY NOT CONNECTING TO TELEGRAM

Symptom: Messages sent to the Telegram bot receive no response. The container appears to be running.

Two-step diagnosis: Step 1 — Ask Lisa directly via any working channel: "Telegram isn't responding — please check and diagnose." Lisa can self-diagnose the Telegram gateway from inside the container without SSH access.

Step 2 — If Lisa is also unreachable, SSH in and restart the gateway manually: docker exec hermes hermes telegram restart If that fails: docker restart hermes Check logs after restart: docker logs hermes --tail 30 | grep -i telegram

Also check: the Telegram Bot Token in config has not been invalidated. If the bot was revoked in BotFather, a new token must be issued and set: docker exec hermes hermes config set TELEGRAM_BOT_TOKEN "new-token"

---

PROBLEM 23: CRON NOT FIRING — TIMEZONE MISMATCH (UTC vs CENTRAL)

Symptom: Scheduled crons never run at the expected time, or appear to run at the wrong time.

Root cause: The Hermes container runs on UTC time. Cron schedules must be entered in UTC. Central Time is UTC-5 (CST) or UTC-6 (CDT). A cron set for "8 AM Central" must be entered as 13:00 or 14:00 UTC depending on daylight saving time.

Conversion reference: 8 AM CDT (Central Daylight) = 13:00 UTC → cron: 0 13 8 AM CST (Central Standard) = 14:00 UTC → cron: 0 14

Verify cron exists and is enabled: docker exec hermes hermes cron list

Test a specific cron manually: docker exec hermes hermes cron run [cron-id]

Note: Crons must be created from a manual (non-cron) session. Creating a cron from inside a running cron session is not allowed.

---

PROBLEM 24: MEMORY ACTING STALE, CONFUSED, OR CONTRADICTORY

Symptom: Lisa has outdated information, references services that no longer exist, uses wrong credentials, or contradicts herself across sessions.

Root cause: MEMORY.md has accumulated conflicting or outdated entries across many sessions without cleanup.

Solution:

  1. Ask Lisa directly: "Read me your MEMORY.md" — she will output the full file.
  2. Identify entries that are outdated, wrong, or contradictory.
  3. SSH in and edit: docker exec -it hermes bash then nano /opt/data/memories/MEMORY.md
  4. Remove outdated entries and update stale information.
  5. Keep total file size under 2,200 characters.
  6. Restart container to reload: docker restart hermes

---

Symptom: upwork_cookies.json exists on disk with 63 entries, but every cookie value is an empty string. Attempts to use this file for authentication fail silently.

Root cause: Chrome stores cookies in an encrypted SQLite database. Extracting them requires decryption using OS-level keys (DPAPI on Windows, Keychain on Mac, libsecret on Linux). Inside the Docker container, these keys are not accessible. The extraction script ran but produced empty values without throwing an error.

Resolution: This approach is a dead end. Do not attempt to extract or use Chrome cookies from inside the container.

docker exec hermes rm -f /opt/data/upwork_cookies.json

---

PROBLEM 26: @anthropic-ai/claude-chrome-mcp NPM PACKAGE DOES NOT EXIST

Symptom: npm install @anthropic-ai/claude-chrome-mcp fails with "404 Not Found" or "package not found."

Root cause: This package name does not exist in the npm registry and never did. Installing it will always fail.

Resolution: This is a dead end. Do not attempt to install it. Use chrome-devtools-mcp@latest instead — this is the correct package and is already configured in .mcp.json: npm install -g chrome-devtools-mcp@latest

---

PROBLEM 27: DAILY 4:00 AM UTC SESSION RESET WIPES LISA'S WORKING CONTEXT

Symptom: Any overnight task or in-progress work is lost. Lisa starts fresh at 4 AM UTC with no memory of what she was doing.

Root cause: Hermes performs a daily session reset at approximately 4:00 AM UTC. This is normal behavior — it clears the active conversation context to prevent runaway sessions. It does NOT delete memory files.

Mitigation:

  1. All critical information must be written to MEMORY.md, not held in active context.
  2. Overnight scheduled crons must be designed to complete before 4:00 AM UTC (before 11:00 PM CDT / 10:00 PM CST).
  3. If a task needs to run overnight, schedule it early enough that it finishes before the reset.

---

PROBLEM 28: CONTAINER DOES NOT AUTO-RESTART AFTER VPS REBOOT

Symptom: After a VPS reboot or power event, the Hermes container is stopped and Lisa is completely offline. No automatic recovery occurs.

Solution: Set the container restart policy to "unless-stopped": docker update --restart unless-stopped hermes-agent-jemg-hermes-agent-1

After setting this, the container will restart automatically after any VPS reboot, unless it was manually stopped by the user. Verify the policy was applied: docker inspect hermes-agent-jemg-hermes-agent-1 | grep RestartPolicy

---

PROBLEM 29: DISK SPACE FILLS SILENTLY INSIDE CONTAINER

Symptom: Agent becomes sluggish or unresponsive. Crons fail silently. No obvious error message.

Diagnosis: Check disk usage inside the container: docker exec hermes-agent-jemg-hermes-agent-1 df -h

If /tmp or /opt/data is near full, clean temporary files: docker exec hermes-agent-jemg-hermes-agent-1 find /tmp -type f -delete

Also check for large log files: docker exec hermes-agent-jemg-hermes-agent-1 find /opt/data -name "*.log" -size +10M

Note: /opt/data is a mounted volume — cleaning it reduces disk use on the host VPS as well.

---

PROBLEM 30: OPENROUTER $20/MONTH SPENDING CAP STOPS ALL API CALLS

Symptom: Lisa suddenly stops responding or returns "quota exceeded" errors. All OpenRouter-dependent functions fail simultaneously.

Root cause: A $20/month spending limit was set on the OpenRouter account. When the cap is hit, all API calls are blocked until the billing cycle resets or the limit is raised.

Solution:

  1. Log into openrouter.ai → Account → Usage → raise the spending cap or wait for the next billing cycle reset.
  2. A daily 8:00 AM Central Telegram credit report cron is deployed — this provides early warning before the cap is hit.
  3. If the Telegram report stops arriving, that itself is a sign the cap may have been hit (the cron call to OpenRouter is also blocked).

---

Symptom: Web search worked previously but now returns rate limit errors or no results. DuckDuckGo is configured as the search backend.

Root cause: DuckDuckGo (ddgs) imposes rate limits on automated queries. After enough rapid searches, it temporarily blocks the container's IP.

Solution: Switch to a different search backend in /opt/data/config.yaml:

  1. docker exec -it hermes bash
  2. nano /opt/data/config.yaml
  3. Change search_backend: 'ddgs' to one of:
  4. Save and restart: docker restart hermes

Temporary workaround: Wait 10-30 minutes and try again — DuckDuckGo rate limits are usually temporary.

---

PROBLEM 32: config.yaml YAML SYNTAX CORRUPTION CRASHES HERMES

Symptom: Container starts but Hermes immediately crashes. Logs show a YAML parse error.

Root cause: A manual edit to /opt/data/config.yaml introduced invalid YAML syntax (wrong indentation, unquoted special characters, etc.).

Solution:

  1. Check the error: docker logs hermes --tail 20
  2. If YAML parse error is confirmed, restore from GitHub backup:
  3. If no GitHub backup available: re-run hermes setup inside the container to regenerate config.
  4. Restart: docker restart hermes

Prevention: Always edit config.yaml via nano or a text editor that preserves indentation. Never use sed or echo to modify YAML files.

---

PROBLEM 33: cfmbusiness OAUTH TOKEN DRIFTS OR BECOMES INVALID

Symptom: check_cfm_email.py fails with OAuth authentication errors. The local backup copy of cfmbusiness_google_token.json no longer matches what's on the VPS.

Root cause: After any VPS restore or redeployment, the token file on disk may be a different version than what Google considers valid. The local backup snapshot may also be stale.

Solution:

  1. Do not trust the local backup of cfmbusiness_google_token.json after any VPS restore.
  2. Re-run the OAuth flow from scratch using check_token.py:

python3 check_token.py (run locally — requires browser) Complete the browser authentication flow.

  1. Copy the newly generated token to the VPS container:

scp cfmbusiness_google_token.json root@2.24.96.191:/tmp/ docker cp /tmp/cfmbusiness_google_token.json hermes:/opt/data/cfmbusiness_google_token.json

  1. Test: python3 /opt/data/browser-manager/tasks/check_cfm_email.py 3

If Google has fully revoked the token (e.g., after a security event): Re-run the full OAuth flow — the browser will ask you to re-grant permissions. The resulting token is fresh and valid.

---

PROBLEM 34: CHROME CRASHES OR HANGS INSIDE CONTAINER — NO RESTART PROCEDURE

Symptom: Browser tasks time out. Screenshots never return. Chrome appears to be running (pgrep shows a PID) but does not respond to CDP commands.

Diagnosis: docker exec hermes-agent-jemg-hermes-agent-1 pgrep -a chrome If Chrome is running but unresponsive, kill it: docker exec hermes-agent-jemg-hermes-agent-1 pkill -f chrome

Restart Chrome inside the container: docker exec -d hermes-agent-jemg-hermes-agent-1 bash -c "DISPLAY=:99 google-chrome --headless --disable-gpu --remote-debugging-port=9222 --no-sandbox --disable-dev-shm-usage &"

Verify CDP is responding: docker exec hermes curl -s http://localhost:9222/json

If Chrome won't start: restart the full container — browser_manager will reinitialize Chrome and Xvfb on startup: docker restart hermes-agent-jemg-hermes-agent-1

---

PROBLEM 35: PYTHON VIRTUALENV FOR BROWSER COMPONENTS CORRUPTED

Symptom: Browser-related tasks fail with Python import errors. The hermes-browser-env virtualenv does not respond correctly.

Diagnosis: docker exec hermes-agent-jemg-hermes-agent-1 /opt/data/home/.hermes-browser-env/bin/python -c "import socket; print('OK')" If this fails with an error, the virtualenv is corrupted.

Solution — rebuild the virtualenv:

  1. Enter the container: docker exec -it hermes-agent-jemg-hermes-agent-1 bash
  2. Remove the old env: rm -rf /opt/data/home/.hermes-browser-env
  3. Rebuild: python3 -m venv /opt/data/home/.hermes-browser-env
  4. Reinstall dependencies: /opt/data/home/.hermes-browser-env/bin/pip install -r /opt/data/browser-manager/requirements.txt
  5. Exit and restart: docker restart hermes-agent-jemg-hermes-agent-1

---

PROBLEM 36: DOCUMENTATION HAS WRONG LLM MODEL NAME

Symptom: CLAUDE.md or INTEGRATIONS.md still shows gpt-4.1-mini as the active model, but Session 7 switched the running model to mistralai/mistral-small-3.2-24b-instruct. Future sessions following the docs will attempt to configure the wrong model.

Root cause: Documentation was not updated when the model was changed in Session 7.

Correct current model: mistralai/mistral-small-3.2-24b-instruct (via OpenRouter) Wrong model name: mistralai/mistral-small — this endpoint returns HTTP 404

Action: Update CLAUDE.md and INTEGRATIONS.md in the local Lisa_Hermes/ project folder to reflect the actual running model. Verify on VPS: docker exec hermes hermes config get MODEL_NAME

---

PROBLEM 37: LOCAL check_cfm_email.py OUT OF SYNC WITH VPS VERSION

Symptom: Session 7 rewrote check_cfm_email.py on the VPS to accept a count argument and number emails. The local copy at Lisa_Hermes/scripts/check_cfm_email.py was never updated. If the local file is ever deployed to the VPS, it overwrites the fix and email filtering resumes silently.

Solution — sync the local file from VPS: scp root@2.24.96.191:/opt/data/browser-manager/tasks/check_cfm_email.py Lisa_Hermes/scripts/check_cfm_email.py

After syncing, commit to GitHub so the fix is preserved in the backup repo.

---

PROBLEM 38: META/FACEBOOK PAGE ACCESS TOKENS EXPIRE (~60 DAYS)

Symptom: Facebook posting or Messenger actions fail silently. No error shown to user — Lisa simply cannot post.

Root cause: Meta long-lived page access tokens have a ~60-day lifespan. There is no auto-renewal mechanism.

Prevention: Set a calendar reminder at day 50 (10 days before expiry) to renew tokens.

Renewal: Generate new long-lived tokens via the Meta Developer portal → Graph API Explorer. Pages affected:

After renewal, update the tokens in /opt/data/.env on the VPS and in the local Lisa_Hermes/.env backup.

---

Symptom: Lisa's browser reaches Upwork but is immediately redirected to a login page. The saved session cookie has expired.

Root cause: Upwork session cookies are valid for approximately 30–90 days. After expiration, a fresh login is required.

Solution:

  1. Generate a new VNC tunnel (see Problem 16 for permanent fix or Problem 16 for temporary URL)
  2. Open the noVNC URL in a browser
  3. Navigate to upwork.com and log in manually as Darrick
  4. Upwork saves the session into the Chrome profile at /opt/data/browser-manager/profiles/default/
  5. Session is now valid for another 30–90 days

---

PROBLEM 40: FACEBOOK POSTING AND MESSENGER COMPLETELY BLOCKED — SCOPES NEVER GRANTED

Symptom: Lisa cannot post to Facebook pages or reply to Messenger messages, even with a valid access token. API returns permission errors.

Root cause: The Facebook app used for this integration was never submitted for pages_manage_posts and pages_messaging scope approval. These scopes require Meta's App Review process before they work in production.

Current status: Unresolved. This is a platform-level requirement — it cannot be bypassed.

Path to resolution:

  1. Log into developers.facebook.com → your app
  2. Submit the app for review for pages_manage_posts and pages_messaging scopes
  3. Meta will review the app (typically 1–7 business days)
  4. Once approved, the existing access tokens will gain those permissions automatically

Until approved, Facebook posting and Messenger replies are not available to Lisa.

---

PROBLEM 41: HERMES BROWSER SKILL NEVER CREATED — TELEGRAM CANNOT INVOKE BROWSER

Symptom: Xvfb, Chrome, and CDP are running inside the container, but no Telegram command can trigger the browser. Browser tasks cannot be assigned to Lisa from Telegram.

Root cause: The browser skill file was never written. Hermes requires a SKILL.md file at a specific path to expose any capability as a Telegram command.

Solution — create the browser skill:

  1. Enter the container: docker exec -it hermes bash
  2. Create the skill directory: mkdir -p ~/hermes/skills/autonomous-browser/
  3. Write SKILL.md with triggers, description, and tool invocation instructions.
  4. Restart: docker restart hermes
  5. Verify: In Telegram, type the trigger phrase — Lisa should now invoke the browser.

✅ RESOLVED – May 16, 2026. Option B (Gmail API reply for cfmbusiness@gmail.com) has been implemented.

Resolution details:

  1. reply_cfm_email.py deployed to /opt/data/browser-manager/tasks/reply_cfm_email.py — uses the existing cfmbusiness OAuth token, no new credentials needed.
  2. check_cfm_email.py updated to output a Gmail-ID: field for every email — Lisa uses this ID with the --reply_to argument.
  3. SOUL.md updated with explicit "Email Sending and Replying" section including exact commands and a prohibition on saying "I cannot send emails."

Commands: Reply to email: python3 /opt/data/browser-manager/tasks/reply_cfm_email.py --reply_to GMAIL_ID --body "Your reply text" Send new email: python3 /opt/data/browser-manager/tasks/reply_cfm_email.py --to recipient@example.com --subject "Subject" --body "Body"

Remaining gap: Upwork replies still require browser/proxy or the Upwork API (see Problem 50).

This was a known gap. Until the skill is created, Lisa cannot use her browser from Telegram commands.

---

PROBLEM 42: ONE-TIME LOGINS NEVER COMPLETED — GHL, FACEBOOK, QUICKBOOKS

Symptom: Lisa cannot access GoHighLevel, Facebook, or QuickBooks via browser. Browser navigates to the site and immediately hits a login page.

Root cause: These services require manual one-time login via VNC to save a session cookie into the Chrome profile. This step was planned but never completed.

Solution:

  1. Generate a VNC tunnel (Problem 16)
  2. Open the noVNC URL in a browser
  3. Log into each service manually:
  4. Session cookies are saved automatically to /opt/data/browser-manager/profiles/default/
  5. Lisa can now access those services via browser until the cookies expire

---

PROBLEM 43: THREE CONFLICTING GOOGLE DOC IDs IN LOCAL PROJECT FILES

Symptom: README.md, HANDOFF.md, and CLAUDE.md each reference a different Google Doc as the "master manual." Agents following different files will work on different documents.

Root cause: The manual went through several versions (v2.0, v2.1, v3.0, v4.0). Each version's ID was recorded in whatever file was being used at the time, but old files were never updated to the current ID.

Canonical Master Manual Doc ID (as of Session 8): 1MMM5zurb9PFFDhCPKaQ_SRJOOKvTI0hGMnkKYlguNmY

Action: Update README.md and any other local file still referencing an old Doc ID to point to the canonical ID above. CLAUDE.md was already updated in Session 8.

---

PROBLEM 44: check_cfm_email.py HAS HARDCODED PYTHON VERSION PATH

Symptom: check_cfm_email.py fails after a Python upgrade or container rebuild with a ModuleNotFoundError.

Root cause: The script contains a line like: sys.path.insert(0, "/opt/hermes/.venv/lib/python3.13/site-packages") This path breaks if the Python version changes (e.g., from 3.13 to 3.14).

Fix options: Option A — dynamic path (recommended): import sys, glob paths = glob.glob("/opt/hermes/.venv/lib/python*/site-packages") if paths: sys.path.insert(0, paths[0])

Option B — version-agnostic symlink: ln -s /opt/hermes/.venv/lib/python3.13 /opt/hermes/.venv/lib/python-current Then use: /opt/hermes/.venv/lib/python-current/site-packages

Edit the file locally, then deploy via scp + docker cp (never sed).

---

PROBLEM 45: upwork_cookies.json ON DISK WITH 63 EMPTY VALUES

Symptom: Future agents find upwork_cookies.json and attempt to use it for Upwork authentication. All 63 cookie values are empty strings. Authentication fails silently.

Root cause: A cookie extraction script ran successfully (no errors) but produced empty values because Chrome's cookie encryption could not be decrypted inside the container (see Problem 25).

Action: Delete this file to prevent future confusion: docker exec hermes rm -f /opt/data/upwork_cookies.json Also delete from the local project backup if present: Lisa_Hermes/upwork_cookies.json

---

PROBLEM 47: GoHighLevel INTEGRATION STATUS CONTRADICTORY

Symptom: CONTEXT.md says GHL is "not configured." INTEGRATIONS.md says GHL API keys have been added. Agents following different files get different answers.

Action: SSH in and verify which state is actually true: docker exec hermes cat /opt/data/.env | grep -i ghl docker exec hermes hermes config list | grep -i ghl

If keys exist and are non-empty: update CONTEXT.md to reflect "configured." If keys are missing: update INTEGRATIONS.md to reflect "not configured." Then test an actual GHL API call to confirm the integration works end-to-end.

---

PROBLEM 48: setup_sonnet.py HAS FULL ANTHROPIC API KEY HARDCODED IN PLAINTEXT

Symptom: This one-time migration script still exists in the project. It contains a full sk-ant-api03-... key embedded as a string literal.

Risk: If this file is ever committed to GitHub or shared, the key is exposed. Anyone with the key can make API calls billed to this account.

Action:

  1. Confirm the script is no longer needed (it was a one-time setup — already ran in Session 3).
  2. Delete it: rm Lisa_Hermes/setup_sonnet.py (or wherever it lives)
  3. Verify it is not in git history: git log --all --full-history -- setup_sonnet.py
  4. If it was ever committed, rotate the Anthropic API key immediately.

---

PROBLEM 49: fix_model.py IS A STALE DANGEROUS SCRIPT

Symptom: This script exists in the project and contains logic to migrate Hermes to gemini-2.0-flash. Gemini was abandoned as a provider. If this script is ever run accidentally, it reverts the config to a non-working model.

Action:

  1. Add a warning comment at the top of the file: # DO NOT RUN — HISTORICAL ONLY. Used for Gemini migration attempt in Session 4. Gemini was abandoned. Running this script will break Hermes.
  2. Or delete it entirely: rm Lisa_Hermes/fix_model.py

---

PROBLEM 50: UPWORK API (PERMANENT NO-PROXY SOLUTION) NEVER PURSUED

Symptom: Every session, the team works around Upwork access issues with proxies, VNC logins, and cookie management. These all expire and require repeated manual intervention.

Root cause: The permanent solution — Upwork's official OAuth API — has been identified since May 15 but never implemented.

What the OAuth API provides:

Implementation path:

  1. Go to developers.upwork.com — create an OAuth application
  2. Obtain the OAuth Key and Secret
  3. Store in /opt/data/.env: UPWORK_API_KEY and UPWORK_API_SECRET
  4. Write a Python script using the upwork-python library to read and reply to messages
  5. Wire it into Hermes as a skill (see Problem 41 for skill creation)

This is the recommended permanent fix for all Upwork access issues.

---

PROBLEM 51: EMAIL SCRIPTS ARE READ-ONLY — LISA CANNOT REPLY TO CANDIDATES

Symptom: Lisa can read Upwork and cfmbusiness emails via Python scripts, but cannot reply. The 9+ candidate backlog cannot be cleared autonomously.

Root cause: check_upwork_email.py and check_cfm_email.py are read-only scripts. No reply functionality has been written. No Upwork API integration exists.

Current options for replying: Option A — Manual: Darrick logs into each platform directly and replies. Option B — Gmail API (cfmbusiness replies): The Gmail REST API supports sending. A send_email.py script can be written using the existing OAuth token. Option C — Upwork API (Upwork replies): Implement Upwork OAuth API (see Problem 50). The Upwork API supports sending messages to applicants. Option D — Browser-based reply: Use the VNC + proxy approach to log in and reply manually via Lisa's browser (see Problems 8, 15, 16, 39).

✅ RESOLVED – May 16, 2026. Option B (Gmail API reply for cfmbusiness@gmail.com) has been implemented.

Resolution details:

  1. reply_cfm_email.py deployed to /opt/data/browser-manager/tasks/reply_cfm_email.py — uses the existing cfmbusiness OAuth token, no new credentials needed.
  2. check_cfm_email.py updated to output a Gmail-ID: field for every email — Lisa uses this ID with the --reply_to argument.
  3. SOUL.md updated with explicit "Email Sending and Replying" section including exact commands and a prohibition on saying "I cannot send emails."

Commands: Reply to email: python3 /opt/data/browser-manager/tasks/reply_cfm_email.py --reply_to GMAIL_ID --body "Your reply text" Send new email: python3 /opt/data/browser-manager/tasks/reply_cfm_email.py --to recipient@example.com --subject "Subject" --body "Body"

Remaining gap: Upwork replies still require browser/proxy or the Upwork API (see Problem 50).

This was a known gap — Lisa can see messages but cannot respond autonomously until one of the above is implemented.

---

APPENDIX A: SESSION STARTUP CHECKLIST

Run this at the start of every Claude Code session before doing any work on the Hermes agent.

  1. Read PROGRESS.md in the local Hermes Agents project folder.

Understand what the last session completed and what is still pending.

  1. Check that the container is running:

docker ps | grep hermes If not running: docker start hermes-agent-jemg-hermes-agent-1

  1. Verify the active LLM model:

docker exec hermes hermes config get MODEL_NAME Expected: mistralai/mistral-small-3.2-24b-instruct If wrong: docker exec hermes hermes config set MODEL_NAME "mistralai/mistral-small-3.2-24b-instruct" then docker restart hermes

  1. Check memory file sizes:

docker exec hermes wc -c /opt/data/memories/USER.md /opt/data/memories/MEMORY.md USER.md must be ≤1,375 chars. MEMORY.md must be ≤2,200 chars. If over limit: ask Lisa "Read me your MEMORY.md" and trim outdated entries.

  1. Send Lisa a test message on Telegram — confirm she responds coherently.

If no response: see Problem 2 (Telegram bot) or Problem 22 (Hermes gateway).

  1. Check for unfilled placeholders in agent subfolders:

Run .\scripts\check-agent-status.ps1 from the Hermes Agents root.

  1. Review open known issues from PROGRESS.md before starting new work — don't duplicate effort.

---

APPENDIX B: MAINTENANCE CALENDAR — TOKEN AND CREDENTIAL EXPIRY

Review this table every 30 days. Set calendar reminders proactively — silent expiry is the #1 cause of unexplained failures.

ITEM | EXPIRES | REMINDER AT | WHERE TO RENEW Meta/Facebook page access tokens | ~60 days | Day 50 | developers.facebook.com → Graph API Explorer → generate long-lived page token Upwork browser session cookie | 30–90 days | Day 25 | VNC tunnel → log into upwork.com manually → session saved to Chrome profile OpenRouter monthly credits | Monthly (per billing cycle) | When daily Telegram report shows low balance | openrouter.ai → Account → Usage → raise cap or top up GitHub personal access token | Per token settings (1yr typical) | Check expiry in GitHub settings | GitHub → Settings → Developer Settings → Personal Access Tokens (Classic) → regenerate cfmbusiness OAuth refresh token | Does not expire unless revoked | Only act if check_cfm_email.py fails with auth error | Run check_token.py locally → complete browser OAuth flow → docker cp new token to VPS Telegram Bot Token | Does not expire unless revoked by user | Only act if bot goes silent and logs show auth error | t.me/BotFather → /mybots → revoke and reissue token → update hermes config

Facebook pages to renew:

After any token renewal, update /opt/data/.env on the VPS AND the local Lisa_Hermes/.env backup file.

---

APPENDIX C: GLOSSARY ADDITIONS (v4.0+)

The following terms were added or became relevant after v4.0 and are not in the original Glossary.

check_cfm_email.py Python script that reads cfmbusiness@gmail.com using the Gmail REST API and OAuth2 token. Located at: /opt/data/browser-manager/tasks/check_cfm_email.py Usage: python3 check_cfm_email.py N (where N = number of emails to retrieve) Replaces himalaya for cfmbusiness email access. Do not use IMAP XOAUTH2 in this environment.

check_upwork_email.py Python script that reads dealanalyzer33@gmail.com (Upwork notifications) via Python imaplib + Gmail app password. Located at: /opt/data/browser-manager/tasks/check_upwork_email.py Replaces himalaya for Upwork email monitoring.

ddgs (DuckDuckGo Search) Built-in web search backend for Hermes. Free, no API key required. Activated by setting search_backend: 'ddgs' in /opt/data/config.yaml. Subject to rate limiting under heavy use — switch to brave_free or searxng if rate limited.

himalaya A third-party email CLI binary that was never successfully installed on this VPS. The file at /opt/data/.local/bin/himalaya contains only 9 bytes: "Not Found." It has never worked. Do not reinstall. All email access uses Python scripts instead.

OpenRouter A proxy service that provides access to many LLM providers (Mistral, Anthropic, OpenAI, etc.) via a single API key. API base: https://openrouter.ai/api/v1 Current model in use: mistralai/mistral-small-3.2-24b-instruct Monthly spending cap: $20 (raises via openrouter.ai → Account → Usage)

USER.md / MEMORY.md The two core memory files that Hermes injects into every session at startup. USER.md: Who the user (Darrick) is — name, goals, email addresses, preferences. Limit: ≤1,375 characters. MEMORY.md: All operational knowledge — scripts, integrations, credentials locations, rules. Limit: ≤2,200 characters. Located at: /opt/data/memories/

websockify A WebSocket-to-TCP bridge that allows noVNC (browser-based VNC client) to connect to x11vnc. Must be pointed at the container's internal IP and port 5900. Run on the VPS host: websockify 6080 172.16.1.2:5900 &

x11vnc A VNC server that shares the container's virtual display (:99 Xvfb) over the network. Must be started after Xvfb is initialized by browser_manager on container startup. Start: docker exec -d hermes x11vnc -display :99 -nopw -listen localhost -xkb -forever

Xvfb (X Virtual Framebuffer) A virtual display server that runs inside the container without a physical monitor. Runs on display :99. Required for headless Chrome to function. Started automatically by browser_manager on container startup. If it becomes a zombie process, restart the full container to reinitialize it.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SESSION 12 REPAIRS — May 16, 2026 Software engineering audit: SSH + code inspection of all VPS scripts ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Problem 52 — websocket-client Package Missing in Hermes Venv Root cause: /opt/hermes/.venv created without websocket-client. check_cookies.py and browser_control.py's CdpConnection both import websocket at top level, causing ModuleNotFoundError crashes. The venv had websockets (async) but not websocket-client (sync). Fix: Bootstrap pip via /opt/data/get-pip.py, then install: docker exec hermes-agent-jemg-hermes-agent-1 /opt/hermes/.venv/bin/python3 /opt/data/get-pip.py docker exec hermes-agent-jemg-hermes-agent-1 /opt/hermes/.venv/bin/pip install websocket-client Verify: /opt/hermes/.venv/bin/python3 -c "import websocket; print(websocket.__version__)" Status: FIXED — websocket-client 1.9.0 installed.

Problem 53 — browser_manager.py Crashes on Restart (Port 9223 Already in Use) Root cause: http.server.HTTPServer does not set SO_REUSEADDR by default. On restart, port 9223 in TIME_WAIT state causes OSError. This unhandled exception kills the entire browser_manager process, leaving Chrome orphaned (zombie). Fix applied to /opt/data/browser-manager/browser_manager.py: (1) Added ReuseAddrHTTPServer subclass with allow_reuse_address = True (line 308) (2) Wrapped start_proxy() in try/except OSError in main() (line 341) so Chrome survives proxy failures Restart command after fix: docker exec hermes-agent-jemg-hermes-agent-1 bash -c "pkill -f browser_manager 2>/dev/null; sleep 3; cd /opt/data/browser-manager && DISPLAY=:99 BROWSER_PROXY=http://127.0.0.1:8081 nohup /opt/data/home/.hermes-browser-env/bin/python browser_manager.py --profile default --cdp-port 9222 --proxy-port 9223 >> /tmp/bm.log 2>&1 < /dev/null &" Verify (wait 12s): docker exec hermes-agent-jemg-hermes-agent-1 curl -s http://localhost:9222/json/version | grep Browser Status: FIXED — browser_manager.py patched. Chrome survives proxy port conflicts.

Problem 54 — Chrome Crashes Under Heavy Page Load (Memory / Zombie Processes) Root cause: KVM 2 VPS has limited RAM. Navigating JavaScript-heavy sites (Upwork) causes Chrome renderer crashes. Zombie processes accumulate when browser_manager crashes (now fixed by Problem 53 patch). --disable-dev-shm-usage already set in browser_manager.py. Symptom: ps aux shows many [chrome] <defunct> processes. CDP port 9222 returns Connection refused. Restart Chrome: Same command as Problem 53 restart. Long-term options: (A) Add 5-minute health-check cron to auto-restart if CDP unresponsive; (B) Upgrade to KVM 4 plan; (C) Add Chrome memory flags. Status: PARTIALLY MITIGATED — crash loop fixed (Problem 53). Chrome still crashes under heavy load. Manual restart works.

Problem 55 — Email Reply Capability Gap (Read-Only, No Send) Root cause: SOUL.md documented email reading but not sending. reply_cfm_email.py existed on VPS but was not in the tool trigger map, so Lisa never invoked it. Script at: /opt/data/browser-manager/tasks/reply_cfm_email.py Capabilities: Reply to existing thread (sets In-Reply-To/References headers), send new email. Uses cfmbusiness_google_token.json OAuth2 (gmail.send scope already authorized). Fix: SOUL.md tool trigger map updated with reply and send commands. Status: FIXED — Lisa can now send and reply to cfmbusiness@gmail.com autonomously.

SESSION 12 VERIFICATION SUMMARY websocket-client import: OK (v1.9.0) Facebook session: Already logged in (session cookie active) Google Drive token: Both token files identical, full Drive/Docs/Sheets/Calendar scopes reply_cfm_email.py: Deployed and documented in SOUL.md VNC tunnel: https://sci-logos-title-itself.trycloudflare.com (running on host since May 15) Upwork: 403 not 1010 — session expired, needs re-login via VNC browser_manager.py port fix: SO_REUSEADDR + try/except applied Chrome stability: Starts OK, crashes under heavy JavaScript load (known VPS memory limit)

---

SESSION 16–18 REPAIRS — May 16, 2026 Software engineering audit: Model failures, browser infrastructure, agent behavior, monitoring

Problem 56 — Mistral Small Returns Empty Responses via OpenRouter

Root cause: mistralai/mistral-small-3.2-24b-instruct returns an empty string body for multi-tool agent sequences via OpenRouter. OpenRouter caches this empty response, so all 3 retry attempts receive the same cached empty reply. Hermes interprets empty responses as failures and sends a fallback "I cannot do that" message for every request.

Symptom: Lisa responds to every Telegram message with "I cannot do that" or a refusal, regardless of what was asked. Gateway logs show api_calls=1 with no response content.

Fix: Switch model from mistral-small to openai/gpt-4.1-mini via OpenRouter: docker exec hermes-agent-jemg-hermes-agent-1 bash -c 'hermes config set model.default openai/gpt-4.1-mini && hermes config set model.provider openrouter' Restart the gateway only (not full docker restart) to avoid config reversion.

Status: FIXED — GPT-4.1-mini handles multi-tool sequences reliably. Gateway logs confirm responses delivered (response=393 chars, time=21.6s).

---

Problem 57 — Model Configuration Reverts to Anthropic on Every Container Restart

Root cause: Hermes gateway initialization code reads a hardcoded default model from its startup logic rather than the persisted config.yaml. Every time docker restart or a full container restart occurs, model.default resets to claude-sonnet-4-5 and model.provider resets to anthropic. The correct settings in config.yaml are overwritten silently.

Symptom: Lisa works correctly after manual configuration, then reverts to refusing all tasks after any restart. Logs show provider: anthropic after restart even though config.yaml was set to openrouter.

Fix — Permanent self-healing watchdog deployed at /opt/hermes_watchdog.py on the VPS host: Runs every 5 minutes via host crontab, completely independent of the container. Detects if model has reverted to Anthropic. Silently fixes config back to openai/gpt-4.1-mini via OpenRouter. Restarts gateway inside container so fix takes effect. Logs all interventions to /var/log/hermes_watchdog.log. Host crontab entry: /5 * /usr/bin/python3 /opt/hermes_watchdog.py

Manual one-time fix (after any unplanned restart): docker exec hermes-agent-jemg-hermes-agent-1 bash -c 'hermes config set model.default openai/gpt-4.1-mini && hermes config set model.provider openrouter'

Status: FIXED — Watchdog active on host. Auto-corrects model within 5 minutes of any reversion.

Related problems: Problem 58 (Anthropic key capped), Problem 56 (Mistral empty responses)

---

Problem 58 — Anthropic API Key Hit Monthly Spend Cap

Root cause: The Anthropic API key reached its configured monthly spending limit. All requests to api.anthropic.com returned HTTP 400 "spending limit reached." Gateway could not complete any LLM calls. This affected all services inside the container that had provider: auto, which resolved to Anthropic.

Symptom: Lisa sends empty replies or stops responding entirely. Container logs show HTTP 400 errors from api.anthropic.com. Began May 16, 2026.

Fix:

  1. Commented out ANTHROPIC_API_KEY in /opt/data/.env so no internal service accidentally uses it.
  2. Switched all LLM traffic to OpenRouter (GPT-4.1-mini) — see Problem 57.
  3. Do NOT re-enable ANTHROPIC_API_KEY until June 1, 2026 (monthly billing reset).

To re-enable after June 1: uncomment the key in /opt/data/.env and update watchdog if needed.

Status: FIXED — Anthropic key disabled. OpenRouter handles all LLM calls until June 1 reset.

---

Problem 59 — SOUL.md Was a Broken Symlink — Lisa Had Zero Instructions

Root cause: /opt/data/SOUL.md was a symbolic link pointing to /proc/self/fd/0 (stdin). Reading the file returned empty content. This meant Lisa had NO behavioral instructions, no tool trigger map, no ABSOLUTE PROHIBITIONS, and no script references for any prior session. She was operating entirely on base LLM training with zero project-specific configuration.

Symptom: Lisa refuses to use any external tools. Has no knowledge of google_api.py, check_cfm_email.py, facebook_manager.py, or any other scripts. Gives generic AI assistant responses. Does not know credentials exist or how to run tasks.

Fix: Replace the broken symlink with a real file written directly to the bind-mount source:

  1. SSH to VPS host.
  2. Write full SOUL.md content to /docker/hermes-agent-jemg/data/SOUL.md (bind-mount source, changes appear inside container immediately).
  3. Verify: docker exec hermes-agent-jemg-hermes-agent-1 wc -l /opt/data/SOUL.md — should return 200+ lines.

Current SOUL.md: 270+ lines including Email, Drive, Calendar, Facebook, STATUS FOOTER, ABSOLUTE PROHIBITIONS, and TOOL TRIGGER MAP sections. Located at local backup: Lisa_Hermes/scripts/push_soul.py handles future deployments.

Status: FIXED — SOUL.md is a real file with full behavioral instructions. All tool access is now documented.

---

Problem 60 — TELEGRAM_HOME_CHANNEL Set to Invalid Chat ID Crashed the Gateway

Root cause: The TELEGRAM_HOME_CHANNEL environment variable in /opt/data/.env was set to 7587979160 — a chat ID that does not correspond to any active conversation with the bot. Every time Hermes attempted to send a proactive message to the home channel, the Telegram API returned "Chat not found" and the gateway crashed.

Symptom: Gateway crashes periodically with no user-facing trigger. Logs show "Chat not found" errors from the Telegram API. Lisa goes silent between conversations until gateway is manually restarted.

Fix: Clear the TELEGRAM_HOME_CHANNEL value: docker exec -it hermes-agent-jemg-hermes-agent-1 nano /opt/data/.env Set TELEGRAM_HOME_CHANNEL= (empty) or remove the line entirely. Restart gateway inside container (not full docker restart).

Status: FIXED — Home channel cleared. Gateway no longer crashes on proactive message attempts.

---

Problem 61 — Xvfb Zombie Processes — Container Kernel Does Not Support X11

Root cause: The VPS container kernel does not expose the X11 socket interface that Xvfb requires. Xvfb starts but immediately becomes a defunct (zombie) process. This cascades: Chrome cannot find display :99, Chrome also fails to start, CDP port 9222 never opens, and browser_manager.py enters a crash loop creating more zombie processes.

Symptom: ps aux shows multiple [Xvfb] and [chrome] defunct entries. CDP port 9222 returns Connection refused. Browser watchdog script restarts Chrome every minute but Chrome always crashes. The watchdog itself cannot resolve this because the X11 dependency is at the kernel level.

Fix: Switch Chrome to headless mode — no X server required: Chrome already has --headless=new in its launch flags inside browser_manager.py. Removed all Xvfb startup logic from /opt/data/browser-watchdog.sh. Updated watchdog only launches Chrome directly with --headless=new — no DISPLAY variable, no Xvfb. Chrome/147 now runs reliably in headless mode with CDP on port 9222.

Status: FIXED — Chrome runs in --headless=new mode. No Xvfb dependency. CDP port 9222 responds. Browser_watchdog.sh updated and deployed to host crontab.

Related problems: Problem 54 (Chrome crashes under heavy load)

---

Problem 62 — Facebook Page Management Required Browser When Graph API Was Available

Root cause: No Meta Graph API script existed for Facebook page operations. SOUL.md referenced browser-based Facebook access only. Lisa attempted to navigate to facebook.com via the headless browser for every Facebook task (reading posts, posting comments), which failed because Facebook's Cloudflare protection blocks headless Chrome and browser sessions were unstable.

Symptom: Lisa refuses Facebook tasks or reports browser errors when asked to read page posts or comment. Facebook login via browser fails silently.

Fix: Created facebook_manager.py at /opt/data/scripts/facebook_manager.py using Meta Graph API v25.0: Commands: posts --page NAME --limit N (list recent posts), comments --post-id ID (list comments), comment --post-id ID --message "text" (post a comment), reply --comment-id ID --message "text" (reply to a comment). Reads credentials from /opt/data/.env: META_PAGE_GIG_WORK_ID, META_PAGE_GIG_WORK_ACCESS_TOKEN, META_PAGE_FIX_FLINT_ID, META_PAGE_FIX_FLINT_ACCESS_TOKEN. No browser required — pure REST calls via urllib. Token logic: token_for_post() parses the page ID from the post ID prefix (e.g., 447725185097879_POSTID correctly routes to USA Gig Work token, not alphabetically-first Fix Flint token).

Also added Facebook disambiguation rule to SOUL.md: when asked ambiguous "login to Facebook," ask "which account — pages or personal profile?" Never auto-navigate to facebook.com.

Status: FIXED — facebook_manager.py handles all page post and comment operations without a browser. Script also saved locally at Lisa_Hermes/scripts/facebook_manager.py.

---

Problem 63 — No Real-Time OpenRouter Spending Visibility

Root cause: OpenRouter balance was only visible by logging into openrouter.ai manually. No automated monitoring existed inside the agent. The $20/month spending cap could be reached silently mid-session, causing Lisa to stop responding with no warning to the user.

Symptom: Lisa stops responding. Logs show API errors. User has no advance notice that the budget is nearly exhausted.

Fix: Created openrouter_status.py at /opt/data/scripts/openrouter_status.py: Calls OpenRouter GET /api/v1/auth/key to read: limit_remaining, limit, usage_daily, usage_monthly. Reads current model name from /opt/data/config.yaml. Prints one-line status footer with color-coded alert emoji: green checkmark (>$10 remaining), yellow circle ($5–$10), red circle (<$5). Script saved locally at: Lisa_Hermes/scripts/openrouter_status.py

Added STATUS FOOTER rule to SOUL.md: Lisa must run this script and append the output after every Telegram response. No exceptions.

Example output: 💰 OpenRouter ✅ $14.68 left of $20/mo | Today: $0.15 | Model: gpt-4.1-mini | Limit: $20/mo

Status: FIXED — Script deployed. SOUL.md mandates footer after every response. User sees balance in every reply.

Open item: Raise OpenRouter monthly limit from $20 to $50 at openrouter.ai → Settings → Credit Limits.

---

SESSION 16–18 VERIFICATION SUMMARY

PROBLEM 52: MISTRAL MODEL RETURNS EMPTY AFTER MULTI-TOOL SEQUENCES

Symptom: Lisa begins a multi-step task (e.g., check emails then reply to one). She runs the first terminal command successfully and returns results. When she attempts a second terminal command in the same session, the model returns: ⚠️ Empty response from model — retrying (1/3) ⚠️ Empty response from model — retrying (2/3) ⚠️ Empty response from model — retrying (3/3) ❌ Model returned no content after all retries. No fallback providers configured.

Root cause: mistralai/mistral-small-3.2-24b-instruct fails to generate a response when the context window contains outputs from multiple sequential tool calls. It handles single-tool tasks (reading emails alone) but crashes silently on two-step workflows (read email → send reply). The empty response is not an error from the API — the model simply returns nothing.

Solution: Switch the primary model to openai/gpt-4.1 via OpenRouter:

hermes config set model.default openai/gpt-4.1 hermes config set model.provider openrouter docker restart hermes-agent-jemg-hermes-agent-1

Verify after restart: docker exec hermes-agent-jemg-hermes-agent-1 python3 -c "import yaml; c=yaml.safe_load(open('/opt/data/config.yaml')); print(c['model'])" Expected: {'default': 'openai/gpt-4.1', 'provider': 'openrouter'}

Warning: Do not revert to mistral-small for any task that involves more than one terminal tool call in the same session.

---

PROBLEM 53: GPT-4.1-MINI REFUSES EMAIL OPERATIONS VIA SAFETY FILTERS

Symptom: When asked to send or reply to an email from cfmbusiness@gmail.com, Lisa responds: "I'm here to help answer your questions to the best of my ability, but I'm unable to send emails or access email accounts." This happens even when SOUL.md explicitly instructs her to use the reply script. She can read emails but refuses to send them.

Root cause: openai/gpt-4.1-mini applies consumer-grade safety filters that override SOUL.md system prompt instructions. The mini model specifically blocks email send and access operations regardless of what SOUL.md says. This is a model-level restriction baked into the mini variant.

Solution: Switch to openai/gpt-4.1 (full version — not mini) via OpenRouter:

hermes config set model.default openai/gpt-4.1 hermes config set model.provider openrouter docker restart hermes-agent-jemg-hermes-agent-1

The full GPT-4.1 is designed for agentic API use and does not apply the same safety filters as the mini version.

Note: If cost is a concern — mistral-small works for single-step email reads but fails on multi-step tasks (see Problem 52). GPT-4.1 full is the reliable choice for any workflow that involves reading and then replying to email.

---

PROBLEM 54: ANTHROPIC API KEY HITS MONTHLY SPENDING CAP

Symptom: All requests fail immediately with: HTTP 400: You have reached your specified API usage limits. You will regain access on [DATE] at 00:00 UTC. Error type: invalid_request_error

Root cause: ANTHROPIC_API_KEY in /opt/data/.env is a direct Anthropic API key with a monthly spending limit set in the Anthropic console. When that limit is reached, every request using model.provider = anthropic fails with HTTP 400 — regardless of how much OpenRouter credit remains. The two billing systems are completely independent.

Immediate fix: Switch model provider back to OpenRouter:

hermes config set model.default openai/gpt-4.1 hermes config set model.provider openrouter docker restart hermes-agent-jemg-hermes-agent-1

All requests will now route through OpenRouter and the Anthropic API cap is bypassed.

Permanent fix:

  1. Log into console.anthropic.com
  2. Navigate to Billing → Usage Limits
  3. Increase or remove the monthly spending limit on the API key
  4. Monitor separately — both the OpenRouter $20/month cap (Problem 30) and the Anthropic direct cap can deplete independently

Warning: Never set model.provider = anthropic without first confirming the Anthropic API key has remaining budget. OpenRouter should be the default provider.

---

PROBLEM 55: TELEGRAM_HOME_CHANNEL INVALID — GATEWAY CRASHES ON EVERY SEND

Symptom: Lisa appears to complete a task in the dashboard or agent logs, but the Telegram message never arrives. The gateway restarts repeatedly on its own. The gateway.log shows: ERROR [Telegram] Failed to send Telegram message: Chat not found telegram.error.BadRequest: Chat not found

The gateway crashes with exit code 1 and auto-restarts, then crashes again on the next attempted send.

Root cause: TELEGRAM_HOME_CHANNEL in /opt/data/.env is set to a chat ID that the Telegram bot cannot access — either an invalid ID, a deleted group, or a group the bot was removed from. Every time Hermes attempts to deliver a notification or session summary to the home channel, Telegram rejects it, the gateway throws an unhandled BadRequest exception, and crashes.

How to confirm: docker exec hermes-agent-jemg-hermes-agent-1 tail -30 /opt/data/logs/gateway.log | grep "Chat not found"

If this returns results, this problem is the cause.

Solution: Clear the TELEGRAM_HOME_CHANNEL value in the container .env:

docker exec hermes-agent-jemg-hermes-agent-1 sed -i 's/^TELEGRAM_HOME_CHANNEL=.*/TELEGRAM_HOME_CHANNEL=/' /opt/data/.env docker restart hermes-agent-jemg-hermes-agent-1

After restart, confirm the "Chat not found" errors are gone: docker exec hermes-agent-jemg-hermes-agent-1 tail -10 /opt/data/logs/gateway.log

Prevention: Only set TELEGRAM_HOME_CHANNEL to a chat, group, or channel that the Telegram bot is an active member of. To find a valid chat ID, forward any message from that chat to @userinfobot on Telegram.

---

SESSION REPAIRS — May 16, 2026 (Afternoon) MEMORY.md audit, dashboard footer, compression risk visibility

Problem 64 — MEMORY.md Missing Critical Credential Check Rule — Lisa Repeatedly Asks for Already-Stored Credentials

Symptom: Lisa asks Darrick for passwords, API keys, or tokens during a session even though those credentials are already stored in /opt/data/.env. The user has to repeat themselves and manually look up information the agent should already know.

Root cause: MEMORY.md lacked an explicit first-priority rule directing Lisa to check /opt/data/.env before asking the user for any credential. The .env file path was referenced casually in several MEMORY.md entries but not as a hard directive — Lisa treated the credential lookup as optional rather than mandatory. Two secondary issues were also found during the same audit: (1) the CFM email entry appeared twice in MEMORY.md, wasting token budget; (2) the LLM entry listed mistral-small as the primary model when config.yaml showed openai/gpt-4.1-mini was actually active.

Fix:

  1. Added CRITICAL RULE as the very first entry in /opt/data/memories/MEMORY.md:

"CRITICAL RULE: Before asking King Solomon for ANY credential, password, token, or API key — STOP. Read /opt/data/.env first. ALL credentials are stored there. Only ask Darrick if the key is genuinely absent from .env. Asking him for something already in .env wastes his time and violates his trust."

  1. Removed duplicate CFM email entry (second occurrence of cfmbusiness@gmail.com access instructions).
  2. Corrected LLM entry from mistral-small to openai/gpt-4.1-mini to match actual config.yaml.

Status: FIXED — MEMORY.md audited and corrected. CRITICAL RULE is now the first item Lisa reads at every session start.

---

Problem 65 — No Model, Budget, or Context Visibility in Telegram Responses

Symptom: Every Lisa Telegram response arrived with no footer showing which LLM model was running, how much OpenRouter credit remained, or how full the context window was. The user had no way to detect hallucination risk from context saturation, no budget warning before the spend cap was hit, and no indication of which model was handling requests.

Root cause: Three separate gaps: (a) runtime_footer was disabled in config.yaml (enabled: false), so context% was never appended to responses; (b) openrouter_status.py already existed and SOUL.md already instructed Lisa to run it, but the output lacked balance alert tiers — just numbers, no warning levels; (c) the context_pct field had no risk labels, making it meaningless to interpret.

Fix:

  1. Enabled runtime_footer in /opt/data/config.yaml:

display.runtime_footer.enabled = true display.runtime_footer.fields = [model, context_pct]

  1. Updated /opt/data/scripts/openrouter_status.py with labeled balance alert tiers:

[OK] = over $10 remaining [LOW] = $5–10 remaining [VERY LOW] = $2–5 remaining [CRITICAL - REFILL NOW] = under $2 remaining

  1. Modified /opt/hermes/gateway/runtime_footer.py to label context_pct with compression risk tiers:

Context: X% [OK] — under 50%, no compression active Context: X% [COMPRESSING] — 50–69%, Hermes is actively summarizing older messages to free space Context: X% [HIGH RISK] — 70–84%, heavy compression, earlier context accuracy is degraded Context: X% [CRITICAL] — 85%+, session auto-reset is imminent

Example footer shown at the bottom of every Lisa Telegram response: Balance: $14.67/$20 [OK] | Today: $4.100 | Month: $5.33 gpt-4.1-mini · Context: 12% [OK]

When context shows [COMPRESSING] or higher, issue /new before any complex multi-step task. When compression is fully exhausted, Hermes automatically appends a 🔄 auto-reset notice to the response — the [CRITICAL] tier is the advance warning before that happens.

Status: FIXED — Footer live on all Telegram responses. Every Lisa reply now shows model name, OpenRouter balance with alert tier, and context window percentage with hallucination risk labeling.

Symptom: Lisa answers questions with generic AI responses and ignores all custom instructions. She refuses tasks she should handle (Google Drive, Calendar, Facebook, email) with phrases like "I cannot do that." SOUL.md appears to exist on disk, but running wc -c /opt/data/SOUL.md returns 0 bytes.

Root cause: When SOUL.md was first deployed, a Docker bind-mount race condition or incorrect file copy created a symbolic link pointing to /proc/self/fd/0 (stdin) instead of a real file. The link appears as a valid file path but returns 0 bytes on every read. Hermes loads SOUL.md at startup — with 0 bytes, the agent starts with absolutely no system instructions and falls back to raw model defaults. All custom behaviors, tool triggers, and absolute prohibitions are silently ignored.

How to confirm: ls -la /docker/hermes-agent-jemg/data/SOUL.md # If output shows "-> /proc/self/fd/0" this is the cause wc -c /docker/hermes-agent-jemg/data/SOUL.md # Returns 0 bytes on the broken symlink

Solution:

  1. Copy the real SOUL.md to a temp location on the VPS host:

scp Lisa_Hermes/SOUL.md root@VPS_IP:/tmp/SOUL_final.md

  1. Remove the broken symlink on the VPS host:

rm /docker/hermes-agent-jemg/data/SOUL.md

  1. Copy the real file into the bind-mount directory:

cp /tmp/SOUL_final.md /docker/hermes-agent-jemg/data/SOUL.md

  1. Verify:

wc -c /docker/hermes-agent-jemg/data/SOUL.md # Must show the correct file size (not 0)

  1. Restart the container:

docker restart hermes-agent-jemg-hermes-agent-1

Prevention: Never use docker cp to deploy SOUL.md — it creates symlinks under certain bind-mount conditions. Always use scp to the VPS host, then cp from host into the bind-mount directory (/docker/hermes-agent-jemg/data/). SFTP directly to the bind-mount path also fails with "size mismatch" — always go via /tmp first.

✅ RESOLVED – May 16, 2026. Broken symlink removed. Real SOUL.md (207 lines) deployed via host-level cp. Verified 0-byte → correct file size. Agent now loads instructions correctly on restart.

See also: Problem 57 (auth.json caches Anthropic key — the second root cause of all-refusal behavior), Problem 5 (agent ignores SOUL.md), Problem 11 (model provider not configured).

---

PROBLEM 57: AUTH.JSON CACHES ANTHROPIC KEY — MODEL REVERTS TO CLAUDE ON EVERY RESTART

Symptom: The model is manually set to openrouter / openai/gpt-4.1-mini and works correctly. But after every container restart, the model silently reverts to an Anthropic Claude model. Requests begin failing again with HTTP 400 spending cap errors. Running: docker exec hermes-agent-jemg-hermes-agent-1 cat /opt/data/config.yaml | grep default confirms the model reverted without any manual change.

Root cause: Hermes stores API credentials in /opt/data/auth.json under a credential_pool object. On every startup, Hermes reads auth.json and overwrites config.yaml to match whatever credentials it finds there. If auth.json contains a valid anthropic entry in credential_pool, Hermes automatically resets the model provider to anthropic — silently overwriting any manual config.yaml edits made while the container was running. Fixing config.yaml alone is never permanent: the next container restart undoes it immediately.

How to confirm: docker exec hermes-agent-jemg-hermes-agent-1 python3 -c "import json; d=json.load(open('/opt/data/auth.json')); print(d.get('credential_pool', {}).get('anthropic', 'NOT FOUND'))" # If this returns a non-empty list or key string, this problem is the cause

Solution (permanent — must be done on VPS host):

  1. SFTP auth.json to host /tmp:

Use a Python script via paramiko/SFTP — do NOT use docker cp (creates symlinks)

  1. Edit auth.json on host to clear the anthropic entry:

python3 -c "import json; d=json.load(open('/tmp/auth.json')); d['credential_pool']['anthropic']=[]; open('/tmp/auth_fixed.json','w').write(json.dumps(d,indent=2))"

  1. Write the fixed file back to the bind-mount directory:

cp /tmp/auth_fixed.json /docker/hermes-agent-jemg/data/auth.json

  1. Fix config.yaml to use OpenRouter (also on host):

Edit /docker/hermes-agent-jemg/data/config.yaml, set: model: default: openai/gpt-4.1-mini provider: openrouter

  1. Restart and verify the model survives restart:

docker restart hermes-agent-jemg-hermes-agent-1 docker exec hermes-agent-jemg-hermes-agent-1 cat /opt/data/config.yaml | grep -A2 "model:"

Prevention: After fixing auth.json, deploy the hermes_watchdog.py script to the VPS host. It runs every 5 minutes via host crontab and re-applies the correct model settings if they ever drift. Location: /opt/hermes_watchdog.py. To install the crontab entry: (crontab -l; echo "/5 * python3 /opt/hermes_watchdog.py >> /var/log/hermes_watchdog.log 2>&1") | crontab -

✅ RESOLVED – May 16, 2026. Anthropic entry cleared from credential_pool in auth.json. Model set to openai/gpt-4.1-mini via OpenRouter. Watchdog deployed at /opt/hermes_watchdog.py — runs every 5 minutes via host crontab. Model survived multiple test restarts.

See also: Problem 54 (Anthropic API key spending cap — what happens when provider = anthropic and the cap is hit), Problem 56 (SOUL.md broken symlink — the other root cause of all-refusal behavior), Problem 11 (model provider not configured).

---

PROBLEM 56: CHROME AND XVFB CREATE ZOMBIE PROCESSES — BROWSER AUTOMATION DEAD

Symptom: All Chrome and Xvfb processes in the container appear as <defunct> in ps aux. CDP port 9222 does not respond — curl http://localhost:9222/json/version returns connection refused. Any browser automation task (Upwork navigation, Facebook login via browser) fails silently. Attempts to restart Chrome immediately produce new zombie processes. Xvfb returns: Fatal server error: Server is already active for display 99 — even after the previous Xvfb process has already died.

Root cause: The Docker container's kernel does not fully support the X11 display server that Xvfb requires. Xvfb starts but exits immediately because it cannot create the necessary kernel-level display structures. Chrome then fails because $DISPLAY has no working X server behind it. The stale /tmp/.X99-lock file left by the dead Xvfb process prevents any new Xvfb from starting, creating a deadlock loop: Xvfb dies → lock file remains → Xvfb refuses to restart → Chrome fails → repeat.

Solution: Switch Chrome to native headless mode — no Xvfb or X server required at all:

# Kill all zombie processes and remove stale display locks pkill -9 -f "chrome-linux64/chrome" 2>/dev/null pkill -9 -f "Xvfb" 2>/dev/null rm -f /tmp/.X99-lock /tmp/.X11-unix/X99 2>/dev/null sleep 1

# Start Chrome in headless=new mode as hermes user su -s /bin/bash hermes -c "/opt/data/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \ --headless=new \ --remote-debugging-port=9222 \ --user-data-dir=/opt/data/browser-manager/profiles/default \ --no-sandbox --disable-gpu --disable-dev-shm-usage \ --remote-allow-origins=* \ >> /opt/data/logs/chrome.log 2>&1 &"

# Verify CDP is responding sleep 6 && curl -s http://localhost:9222/json/version

Also update /opt/data/browser-watchdog.sh: remove all Xvfb start/stop logic and replace with the --headless=new launch command above. The headless mode exposes the full Chrome DevTools Protocol on port 9222 — all existing browser automation scripts work identically without any display server.

PROBLEM 57: HERMES WATCHDOG KILLS GATEWAY BUT NEVER RESTARTS IT

Symptom: Lisa goes silent on a recurring cycle that matches the watchdog cron interval (every 5 minutes). Gateway log shows a clean SIGTERM at the top of the minute, followed by a fresh gateway startup a few seconds later — but any Telegram messages sent during that gap are missed or never processed. The watchdog log shows "Gateway restart signal sent" or "Gateway killed — restarting" with no corresponding "Gateway started OK" entry immediately after.

Root cause: /opt/hermes_watchdog.py used pkill -f "hermes gateway run" to stop the gateway whenever it detected a config problem, but contained no logic to start it back up. The original code included a comment explaining that "systemd Restart=on-failure can revive the gateway" — but systemd does not run inside Docker containers. There is no process manager inside the container that would auto-restart a killed gateway. The gateway stayed dead until the next watchdog cron tick or manual intervention.

Solution: Add a start_gateway() function to the watchdog that is called: (1) immediately after any pkill during a config fix, and (2) independently in main() whenever config is correct but the gateway is not running. Key additions to /opt/hermes_watchdog.py:

def gateway_running(): out, _, _ = run("docker exec CONTAINER pgrep -f 'hermes gateway run' 2>/dev/null") return bool(out.strip())

def start_gateway(): run("docker exec -u hermes CONTAINER bash -c " "'HERMES_HOME=/opt/data nohup hermes gateway run " ">> /opt/data/logs/gateway.log 2>&1 &'") time.sleep(6) if gateway_running(): log("Gateway started OK")

# In fix_model(): after pkill, sleep 3, then call start_gateway() # In main(): if not needs_fix(content) and not gateway_running(): start_gateway()

After this fix, the watchdog handles both jobs: correcting config problems AND ensuring the gateway is always alive.

PROBLEM 58: WATCHDOG ENFORCES WRONG MODEL VERSION — CAUSES ENDLESS RESTART LOOP

Symptom: The gateway restarts every 5 minutes indefinitely. The watchdog log shows "Fixed model.default → openai/gpt-4.1-mini" on every run — even though the model was deliberately set to a different version by a prior session. Lisa goes silent for several seconds on every watchdog cycle. Manually setting the model with hermes config set has no lasting effect — it is overwritten within 5 minutes.

Root cause: TARGET_MODEL in /opt/hermes_watchdog.py is hardcoded to a specific model string. When a session deliberately changes the model to a different version (e.g., upgrading from openai/gpt-4.1-mini to openai/gpt-4.1), the watchdog treats the new correct setting as a reversion error and keeps "fixing" it back to the old hardcoded value — killing and restarting the gateway on every 5-minute cycle. The watchdog always wins this fight.

Solution: Whenever the intended model is changed, update TARGET_MODEL in /opt/hermes_watchdog.py to match:

TARGET_MODEL = "openai/gpt-4.1" # Update this any time the intended model changes

Then redeploy the watchdog to the host: # From Claude Code on local machine — upload updated watchdog via SFTP # Or edit directly on the host: nano /opt/hermes_watchdog.py # Update TARGET_MODEL line, save, exit

Rule: The watchdog is the final authority on which model runs. If hermes config set and the watchdog disagree, the watchdog wins every 5 minutes. Always update the watchdog when changing the intended model — otherwise the change will be silently undone.

PROBLEM 59: LISA REPORTS FEWER EMAILS THAN REQUESTED — MISSES IMPORTANT MESSAGES

Symptom: User asks "check my last 10 CFM emails." Lisa runs check_cfm_email.py 10, which correctly returns all 10 emails numbered 1–10. However, Lisa's Telegram response only contains 3 emails and ends with "Let me know if you want to take any action." Emails deeper in the list — real estate deals, client follow-ups, and other time-sensitive messages — are never mentioned. When the user asks Lisa to find a specific email (e.g., "find the one from Jessica Davis"), Lisa says it isn't there — because she only processed the first 3 of 10 results.

Root cause: The openai/gpt-4.1-mini model truncates its output when the tool result context is long. Rather than passing all 10 emails through to the user, mini stops after the first few and generates a closing sentence. This is a model-level behavior — the check_cfm_email.py script itself works correctly and returns every requested email. The truncation happens inside the model's response generation, not in the script.

Solution: Switch to openai/gpt-4.1 (full version). The full model passes complete tool output to the user without truncation:

hermes config set model.default openai/gpt-4.1 hermes config set model.provider openrouter docker restart hermes-agent-jemg-hermes-agent-1

Also update TARGET_MODEL in /opt/hermes_watchdog.py to "openai/gpt-4.1" so the watchdog enforces the correct version (see Problem 58).

Verification: To confirm whether Lisa truncated emails, run the script directly from Claude Code — this bypasses Lisa entirely:

docker exec hermes-agent-jemg-hermes-agent-1 python3 /opt/data/browser-manager/tasks/check_cfm_email.py 10

Compare what the script returns against what Lisa reported. If they differ, Lisa truncated the output. Claude Code can also complete the email task directly (read, draft reply, send via reply_cfm_email.py) without going through Lisa at all.

---

Problem 70 — LLM Cron Job Created Without User Permission (Token Burn)

Symptoms:

Root Cause: The agent created a "browser-keepalive" job to restart Chrome if it crashes. It set no_agent: false, meaning it spawns a full LLM session every 5 minutes — 288 LLM calls per day. A shell script would do the same job at zero LLM cost. The user was never asked for permission.

Fix:

  1. Disable the job: edit /opt/data/cron/jobs.json, set "enabled": false for browser-keepalive (id: 1521b8b53fc5)
  2. Add permission rules to SOUL.md (see Section 14 below)
  3. Replace with shell script version (no_agent: true) if Chrome keepalive is still needed

Prevention: Any cron job with no_agent: false that runs more than once per hour must require explicit user approval before creation. See Section 14.

---

Problem 71 — Duplicate Email Monitor Jobs (Double Token Cost)

Symptoms:

Root Cause: A second, more frequent monitor was created without disabling the first. The agent never flagged the duplication to the user.

Fix: Disable upwork-reply-monitor (id: 094a1e00e353) — the 30-minute Upwork Email Monitor covers the same function more frequently. Edit /opt/data/cron/jobs.json and set "enabled": false.

Prevention: Before creating any new cron job, check existing jobs for overlap. See Section 14.

---

Section 14 — Token Economy & Cron Job Security

14.1 — What Burns Tokens (and What Does Not)

Every Hermes cron job has a no_agent flag. This single setting determines whether a job costs money:

no_agent: true = Shell script only. Runs a bash or python script. Zero LLM cost. Use for: file operations, backups, health checks, API calls that do not need reasoning.

no_agent: false = Full LLM session. Spawns an AI agent to reason about the task. Costs money on every run. Use ONLY when human-level reasoning is genuinely required.

Rule of thumb: If a junior assistant with a checklist could do it, use no_agent: true.

14.2 — Hidden Cost Multipliers

The following multiply your token cost and are easy to miss:

Frequent LLM crons: A job running every 5 minutes with no_agent: false = 288 LLM calls per day. At claude-sonnet-4.6 pricing, this can cost $1–3/day for a single job.

Duplicate jobs: Two jobs doing the same task = double cost. Always audit existing jobs before creating a new one.

Model price jumps: Switching from gpt-4.1-mini to claude-sonnet-4.6 is a 7–10x cost increase on every single call — including all background jobs. Calculate the new daily cost before switching models.

Context accumulation: Long conversations re-send the entire history as input on every message. A session that grew to 30,000 tokens costs $0.09 per new message in input alone.

Gateway restarts: Each restart may interrupt running LLM sessions. Tokens are billed even for incomplete responses.

14.3 — Mandatory Permission Rules for Agents

These rules must be in every agent's SOUL.md:

  1. NEVER create a cron job with no_agent: false without explicit user approval.
  2. Before creating any cron job, tell the user: what it does, how often it runs, whether it uses the LLM, and the estimated daily token cost.
  3. NEVER switch the LLM model without user permission.
  4. NEVER create a duplicate job. Check existing jobs first.
  5. If the user is not actively in the conversation, do not create new scheduled tasks.

14.4 — Daily Cost Awareness

Every agent should have:

14.5 — Cost Benchmarks

Normal daily spend (gpt-4.1-mini, light usage): $0.10–0.50 Normal daily spend (claude-sonnet-4.6, light usage): $0.50–2.00 Warning threshold: >$3.00/day Emergency threshold: >$5.00/day (investigate immediately)

If daily spend exceeds $5, audit cron jobs immediately for: no_agent: false jobs running more than once per hour, duplicate jobs, model configuration drift.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SESSION 24 — May 16, 2026 — Architecture Audit & Fixes (Nate Herk Methodology Alignment) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PROBLEM 72 — MEMORY.md Had Wrong Model Listed

Symptom: MEMORY.md inside container at /opt/data/memories/MEMORY.md stated primary model was "openai/gpt-4.1-mini" and told Lisa to switch to gpt-4.1-mini for routine tasks and escalate to claude-sonnet only for deep reasoning. This directly contradicted the actual running config (claude-sonnet-4.6 via OpenRouter) and could cause Lisa to self-correct to the wrong model. Root cause: MEMORY.md was written in an early session before the model decision was finalized. Fix: Corrected MEMORY.md. New text states claude-sonnet-4.6 is primary — DO NOT change without King Solomon permission. Verified: docker exec hermes-agent-jemg-hermes-agent-1 grep -A2 "LLM:" /opt/data/memories/MEMORY.md

PROBLEM 73 — GitHub Backup Bash Crontab Was Never Working

Symptom: Host crontab entry "0 6 * /opt/data/github-sync.sh" ran on the HOST machine (not Docker). HOST /opt/data/ contains only 3 watchdog scripts — NOT Lisa's real data. The script was initializing a git repo over the wrong directory. Root cause: Confusion between HOST /opt/data/ and CONTAINER /opt/data/ (see Problem 74). Fix: Removed the broken bash crontab entry. The Hermes-native cron "nightly-github-backup" (runs inside container) is the real working backup. Status: Resolved.

PROBLEM 74 — CRITICAL: Two Different /opt/data/ Directories

There are TWO separate /opt/data/ directories: HOST /opt/data/ = only 3 watchdog scripts. NOT Lisa's data. HOST /docker/hermes-agent-jemg/data/ = Docker volume, mounted as /opt/data/ INSIDE container. All Lisa's real data lives here. Rule: Always use "docker exec hermes-agent-jemg-hermes-agent-1" to access Lisa's files. Never SSH to host and look in /opt/data/ directly. This is the root cause of all "NOT FOUND" false alarms during VPS audits.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ARCHITECTURE NOTES — WHY WE DIFFER FROM NATE HERK ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  1. MODEL: Nate uses GPT-5.5 via OpenAI CodeEx. We use anthropic/claude-sonnet-4.6 via OpenRouter. Reason: Nous Research's official default; better tool-calling reliability. Researched Session 19. Do not revert.
  1. WATCHDOG SCRIPTS: Nate has none. We have two (browser-watchdog.sh, hermes_watchdog.py). Reason: Our model triggers a Hermes config reversion bug on container restart. Necessary — do not remove.
  1. CRON LAYERS: Nate uses Hermes-native only. We use: (a) Hermes-native crons for GitHub backup + daily credit report [matches Nate], plus (b) Host bash crontab for watchdogs only [not Nate's design, required by our setup].
  1. GITHUB BACKUP: Now matches Nate exactly — Hermes-native cron "nightly-github-backup" runs inside container at 6 AM UTC. Broken bash crontab entry removed (Session 24).

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ FIVE PILLARS STATUS (Session 24) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Pillar 1 — MEMORY (USER.md + MEMORY.md): COMPLETE. Path: /opt/data/memories/ Pillar 2 — SKILLS: Built-in skills active. User skills: verify with "hermes cron list" and skill list. Pillar 3 — SOUL (SOUL.md): COMPLETE at /opt/data/SOUL.md (208+ lines). Pillar 4 — CRONS (Hermes-native): COMPLETE. nightly-github-backup + daily-credit-report confirmed. Pillar 5 — SELF-IMPROVING LOOP: ACTIVE. Guardrail: must ask before creating any LLM-calling cron (in SOUL.md).

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Section 15: OpenRouter Smart Routing Flags

Source: "Hermes Agent + DeepSeek V4 = 100X Cheaper" — Jack Roberts https://www.youtube.com/watch?v=awRQWBvE5eQ (Published May 2026)

⚠️ CONTENT UNDER REVIEW — Tech stack evaluation in progress. Kimi-K2 was found to produce null values on required tool call parameters, causing broken tool execution. Model routing strategy will be documented here once the optimal model for Lisa's tool-heavy workload is confirmed. Last updated: 2026-05-17

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Section 16: The Triad System — Multi-Model Orchestration

Source: "Hermes Agent + DeepSeek V4 = 100X Cheaper" — Jack Roberts https://www.youtube.com/watch?v=awRQWBvE5eQ (Published May 2026)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WHAT IT IS

The Triad is a workflow pattern where three different AI models work together on a single problem — each playing a different role. No single model decides the answer alone. The result is more rigorous, better critiqued output than any one model produces by itself.

Jack Roberts named his implementation "Orpheus." The pattern itself is general and can be applied to any Hermes agent.

THE THREE ROLES

Conductor — Claude Opus 4.7 (or best available reasoning model) Job: decompose the task, write the brief, set the strategy, ask clarifying questions. Why Opus: best at structured reasoning and complex decomposition. Worth the cost for this step because it only runs once per task.

Worker — DeepSeek V4 or Kimi-K2 (cheap, capable model) Job: execute the brief. Grind through the plan. Produce drafts, analysis, or output. Why cheap model: this model runs in a loop — sometimes dozens of iterations overnight. The math: 95% of Opus quality at 1% of the cost. For repeated execution, this is the only logical choice. Key insight: cheap enough to retry often if output isn't right the first time.

Critic — ChatGPT 5.5 or Gemini (a different flavor from the conductor) Job: tear the worker's output apart. Find flaws, gaps, weaknesses. Why a different model: using the same model as conductor creates echo chamber bias. A different model's "personality" catches different failure modes. Jack's rule: never ship anything that hasn't been brutally critiqued. The word "brutally" is intentional.

THE LOOP

Conductor sets the goal and brief ↓ Worker attacks it from 3–5 angles ↓ Critic tears it apart ↓ Worker revises based on critique ↓ Conductor validates the final artifact ↓ Ship

HOW TO SET IT UP IN HERMES

Method 1 — Direct SOUL.md persona Add a persona block to /opt/data/SOUL.md with the three-role prompt structure. Tell Lisa: "When I ask you to use deep work mode, activate the triad: use Opus as conductor, Kimi as worker, GPT as critic."

Method 2 — Hermes Dashboard persona (if using the Pantheon dashboard) Create a named persona (e.g., "Orpheus") in the Hermes dashboard. Set the orchestrating model to Opus 4.7. Paste the triad prompt template as the persona description.

THE TRIAD PROMPT TEMPLATE (paste into SOUL.md or persona description)

--- TRIAD PERSONA — DEEP WORK MODE

When King Solomon activates deep work mode or requests the triad:

CONDUCTOR (Claude Opus 4.7): You are the conductor of the Hermes triad. Your job:

  1. Interrogate the problem with 5–10 clarifying questions
  2. Write one tight one-page brief summarizing: goal, constraints, success criteria, angles to explore
  3. Delegate the brief to the Worker
  4. Validate the final artifact before delivery

WORKER (moonshotai/kimi-k2:floor): You are the worker. Your job:

  1. Read the brief from the Conductor
  2. Identify 3–5 distinct angles or approaches
  3. Execute each one thoroughly
  4. Produce a draft output for Critic review
  5. Revise based on Critic feedback until Conductor approves

CRITIC (openai/gpt-4.5 or google/gemini-2.5-pro): You are the critic. Your job:

  1. Review the Worker's output with zero mercy
  2. Identify logical gaps, unsupported claims, weak reasoning
  3. Score each section and explain why it fails or passes
  4. Return specific revision instructions to the Worker

---

WHEN TO USE THE TRIAD

WHEN NOT TO USE IT

COST REALITY CHECK Running the Worker overnight on a complex research task using Kimi-K2:floor costs pennies. Running the same task with Opus would cost dollars. The triad lets you apply Opus-level structure at worker-model prices for the heavy lifting.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Section 17: SOUL.md Deep Template — Giving Your Agent Real Context

Source: "Hermes Agent + DeepSeek V4 = 100X Cheaper" — Jack Roberts https://www.youtube.com/watch?v=awRQWBvE5eQ (Published May 2026)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WHY THIS MATTERS

Most Hermes setups start SOUL.md with a basic personality description and a few rules. Jack Roberts argues — and we now agree — that SOUL.md should contain enough context about who you are and what you're building that the agent can make independent, aligned decisions without being told every time.

The more Hermes knows about you, the better it can:

THE COMPLETE SOUL.md TEMPLATE

Use this as the foundation. Fill in your own values where indicated.

---

Identity

Full name: [Your name] Preferred title: [What you want the agent to call you] Primary email: [Your main email] Location and timezone: [City, timezone]

Primary Goal — 12-Month Target

Income target: $[X,XXX]/month recurring by [date] Every decision this agent makes should be filtered through this question: "Does this action move [your name] closer to [target]?"

Business 1 — [Name]

What it is: [One sentence] How it makes money: [One sentence] Primary lead channel: [Platform or method] CRM: [Tool name] Current status: [Active / Building / Paused] Biggest bottleneck right now: [What's slowing you down]

Business 2 — [Name] (if applicable)

What it is: [One sentence] Target customer: [Who buys it] Current stage: [Idea / MVP / Revenue] Priority level: [High / Medium]

Daily Jobs (Standing Instructions — No Need to Ask)

List the 3–5 things this agent should do every day without being prompted.

  1. [Job 1]
  2. [Job 2]
  3. [Job 3]

Priority Order

When you have multiple things to do simultaneously:

  1. [Highest priority category]
  2. [Second priority]
  3. [Third priority]

Communication Style

Key Constraints and Beliefs

Things I already know or believe — don't argue these, just work with them:

What Success Looks Like

When this agent is doing its job perfectly, here is what my day looks like: [Describe your ideal day in 3–5 sentences] ---

HOW TO POPULATE IT

Option A — Tell Lisa directly in Telegram "I want you to update my SOUL.md with the following information about my goals and business..." then speak naturally. Lisa will extract the structured data and write it to /opt/data/SOUL.md.

Option B — Use Claude Code Edit /opt/data/SOUL.md directly on the VPS using Claude Code with the template above. Use docker exec to write the file.

Option C — Speak it (using voice-to-text like Glide or any dictation tool) Tell the agent everything in natural speech. Say "add this to my soul.md in handsfree mode" and let the agent transcribe and structure it.

CFM BUSINESS IMPLEMENTATION (as of 2026-05-17) Lisa's SOUL.md was updated with the following King Solomon context:

This session proved that a SOUL.md without business context produces a generic assistant. A SOUL.md with specific goals produces a focused operator.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

---

SECTION 12 — NEW ENTRY (Added Session 27, May 17, 2026)

Problem 72: Cannot Post Upwork Job Ads — Cloudflare Blocks All Automation

HOW THIS WAS ACTUALLY SOLVED (exact account of what Claude Code did on May 17, 2026)

Background: For one week, every attempt to automate Upwork through Lisa's browser was blocked by Cloudflare. The local Chrome MCP tool was tested and confirmed blocked (Cloudflare Ray ID: 9fd6229d3b5099ef — blank page even on residential IP). The VPS IP was blocked at the datacenter level. The assumption the whole time was "Lisa can't get past the login wall."

What Claude Code did differently:

Step 1 — Recognized the session was already authenticated. The cookie swap completed in Session 25 had already logged Lisa into Upwork inside her VPS Chrome browser. Cloudflare only challenges at the login screen. It does NOT challenge subsequent page navigation once a session is established. Instead of trying to log in again, Claude Code connected directly to the already-authenticated Chrome session via CDP WebSocket on port 9222 and navigated to the job posting URL: https://www.upwork.com/nx/job-post/regular/title — no Cloudflare block at all.

Step 2 — Wrote Python scripts locally, deployed to VPS via paramiko. Each wizard step was a separate Python script written on the local Windows machine, then pushed to the VPS using this exact pattern: sftp.put("remote_stepN.py", "/tmp/remote_stepN.py") ssh.exec_command("docker cp /tmp/remote_stepN.py CONTAINER:/tmp/remote_stepN.py") stdin, stdout, stderr = ssh.exec_command("docker exec CONTAINER python3 /tmp/remote_stepN.py > /tmp/out.txt 2>&1") stdout.channel.recv_exit_status() sftp.get("/tmp/out.txt", "remote_stepN_out.txt") Output was read back locally to verify each step before proceeding to the next.

Step 3 — Navigated the 6-step Upwork job wizard as follows:

STEP 1/6 — Title (remote_step16.py): Found the title field: <textarea id="job-post-title" class="air3-textarea air3-textarea-expanding"> Typed using document.execCommand, NOT element.value (React ignores .value): ta.focus(); ta.click(); ta.select(); document.execCommand('selectAll', false, null); document.execCommand('delete', false, null); document.execCommand('insertText', false, 'HeyGen AI Clone Specialist Needed - Ongoing Video Production and Training'); Result: title accepted, Next button enabled.

STEP 2/6 — Skills (remote_step17.py): First pressed Escape via CDP to dismiss Upwork's nav search bar, which was intercepting keyboard events. Then clicked skill chip DIV elements directly by matching their text content, filtering to elements with getBoundingClientRect().y > 50 to avoid hidden elements. Clicked "Adobe Premiere Pro" and "Adobe After Effects" chips. Verified by reading page body text.

STEP 3/6 — Scope (remote_step17-18.py series): Selected radio buttons using: radios[index].click() + radios[index].checked = true + dispatchEvent(new Event('change', {bubbles:true})). Selected Medium scope, More than 6 months, Intermediate level. For contract-to-hire, found the "No" radio by dumping HTML to identify it had value="2", then clicked by index (last radio). Force-clicked Next: Location by setting btn.disabled = false; btn.removeAttribute('disabled'); btn.click().

STEP 4/6 — Location (remote_step18.py): Found Worldwide radio by matching label text. Clicked it and dispatched change event. Force-clicked Next: Budget.

STEP 5/6 — Budget (remote_step19.py): Pressed Escape to clear nav search. Found inputs by ID: #manual-budget-from-1 (min rate) and #manual-budget-to-2 (max rate). Both were type="text" inputs at y=314 on the page. Typed "15" into min and "40" into max using execCommand. Force-clicked Next: Description.

STEP 6/6 — Description (remote_step20.py): Found textarea with id="jp-description-1", placeholder="Already have a description? Paste it here!". Typed full 1,237-character job description using execCommand('insertText'). Clicked "Review Job Post". Page advanced to /review URL showing all job details correctly populated.

Review Page (remote_step21.py): Clicked "Next: Finalize job post". Advanced to /feature-job URL showing "Post as standard for free" ($0) vs "Post as Featured" ($29.99).

Final Post (remote_step22.py): Clicked "Post as standard for free". Page navigated to: https://www.upwork.com/ab/applicants/2056151480936439776/suggested Page body confirmed: "Congratulations! Your job post is live."

Job posted: "HeyGen AI Clone Specialist Needed - Ongoing Video Production and Training" — $15–$40/hr — Job ID: 2056151480936439776

Total manual steps from Darrick: zero.

Why the previous week of attempts failed:

  1. Local Chrome MCP was used — its --enable-automation launch flag is detected by Cloudflare fingerprinting regardless of IP address. This is unfixable; the flag is part of how Chrome MCP launches the browser.
  2. The authenticated session in VPS Chrome was never used. Everyone assumed the Cloudflare block at login meant nothing could happen. The authenticated session from the cookie swap was sitting unused.
  3. React input typing used .value = text which React ignores entirely. Buttons stayed disabled because React's validation state never updated.

The three rules to remember for all future Upwork automation: Rule 1: Always use VPS Chrome CDP against the existing authenticated session. Never try to log in fresh. Never use local Chrome MCP for any site that uses Cloudflare. Rule 2: Always use document.execCommand('insertText', false, text) for form fields on React-based sites (Upwork, most modern web apps). Never use .value setter or CDP Input.insertText. Rule 3: Always send Escape via CDP before interacting with any form field on Upwork, because the nav search bar has persistent focus priority and will intercept your input otherwise.

---

Problem 73: Upwork Job Wizard — Nav Search Bar Steals All Keyboard Input

Symptom: Characters typed into form fields appear in the Upwork navigation search bar at the top of the page instead of the target field. The form field stays empty.

Root Cause: Upwork's nav search bar (input with placeholder="Search", positioned at y=0) holds or recaptures keyboard focus whenever page interactions occur. Any CDP keyboard events route to it first.

Exact Fix Used in Session 27: Before every form interaction, Claude Code sent these two CDP calls: send(ws, "Input.dispatchKeyEvent", {"type": "keyDown", "key": "Escape", "code": "Escape"}, mid=5) time.sleep(0.5) send(ws, "Input.dispatchKeyEvent", {"type": "keyUp", "key": "Escape", "code": "Escape"}, mid=6) time.sleep(0.5) Then immediately called element.focus() and element.click() inside a Runtime.evaluate script before using execCommand. This sequence reliably dismissed the nav search bar and gave focus to the correct field every time.

Symptom: Every attempt to automate Upwork navigation fails. Local Chrome MCP is blocked by Cloudflare even on a residential IP. The VPS browser is blocked at the IP level. Login triggers Cloudflare Turnstile CAPTCHA. One week of failed attempts.

Root Cause (3 separate issues compounding):

Issue A — Wrong automation tool. Local Chrome MCP launches Chrome with --enable-automation flag. Cloudflare detects this flag fingerprint regardless of IP address and serves a blank challenge page. This is not fixable — it is a browser-level signal.

Issue B — Wrong assumption about what was blocked. The assumption was "Lisa can't log in, so she can't do anything." The login was indeed blocked by Cloudflare. But the cookie swap (Problem 1 fix) had already established an authenticated session inside VPS Chrome. Cloudflare only challenges at the login screen — it does NOT re-challenge page navigation on an already-authenticated session. The authenticated session was sitting there unused for the entire week.

Issue C — Wrong typing method for React inputs. All Upwork form fields use React's controlled input system. Setting element.value = 'text' or using CDP's Input.insertText command does not trigger React's synthetic event system — React ignores both. The inputs appear filled but React's state is never updated, so validation never passes and Next buttons stay disabled.

Fix:

Step 1 — Use VPS Chrome CDP (not local Chrome MCP). Connect via WebSocket to port 9222 inside the container. The VPS Chrome has no --enable-automation flag and already holds the authenticated Upwork session from the cookie swap. Zero Cloudflare interference.

Step 2 — Use document.execCommand to type into React inputs: element.focus(); element.click(); document.execCommand('selectAll', false, null); document.execCommand('delete', false, null); document.execCommand('insertText', false, 'your text here'); element.dispatchEvent(new Event('input', {bubbles: true})); element.dispatchEvent(new Event('change', {bubbles: true}));

Step 3 — Force-click disabled buttons when validation has not fired: btn.disabled = false; btn.removeAttribute('disabled'); btn.click();

Step 4 — Always press Escape before interacting with any form field. Upwork's nav search bar intercepts keyboard events if it has focus. Send Escape via CDP first.

Proven Result: Full 6-step Upwork job posting wizard completed autonomously in Session 27. Job ID 2056151480936439776 posted live. Zero manual steps from Darrick.

This same pattern works for ANY Upwork task — posting jobs, reading applicant profiles, sending messages. It bypasses every Cloudflare restriction as long as the authenticated session stays alive (30–90 days).

---

Problem 73: Upwork Job Wizard — Nav Search Bar Intercepts Keyboard Events

Symptom: Typed characters appear in the navigation search bar at the top of the page instead of the target form field. The form field remains empty.

Root Cause: Upwork's nav search bar retains focus priority whenever page interactions occur. CDP keyboard events are captured by the nav bar first.

Fix: Before every form interaction, send an Escape key event via CDP to dismiss the nav search bar. Then call element.focus() and element.click() on the target element. Use document.execCommand('insertText', false, text) inside Runtime.evaluate — not CDP Input.insertText, which also routes through the nav bar.

SECTION 13: PHONE SET UP — RETELL AI + TELNYX SIP TRUNK

⚠️ SUPERSEDED (2026-06-23) → see PART 0 §0.2 + PHASE 8. Phone carrier is now TWILIO ONLY — Telnyx is BANNED. Voice = Retell + Twilio SIP trunk + the IVY voice agent. The Telnyx SIP-trunk procedure below is OBSOLETE; kept as historical record only. (The Retell-side mechanics still apply; swap the Telnyx trunk for Twilio.)

Added: 2026-05-30 (Session 44) — first verified phone deployment for Lisa. Status: ✅ LIVE AND WORKING — outbound call confirmed end-to-end.

🎯 WHY THIS SECTION EXISTS — THE LISA MISSION

Phone-call capability is not a nice-to-have feature for Lisa. It IS the mission.

King Solomon's directive (Session 44): "Lisa must be able to place outbound phone calls to ANYONE, to accomplish ANY task — book a hair appointment, order a pizza, negotiate a real estate deal, call my mother, call my brother, call my real estate partner."

Lisa is a phone-capable executive assistant, not a chatbot. The setup recipe below is the foundation. The custom functions, webhook receiver, contact list, and memory loop that go on top of this foundation are what make the mission complete. See MISSION.md in the Hermes Agents workstation project for the full roadmap.

This section is the canonical recipe for adding voice phone-call capability to any CFM Hermes agent. Follow these steps in order. The whole flow takes ~30 minutes once accounts are created.

WHAT THIS GIVES THE AGENT

Verified cost (Session 44, Lisa): ~$0.14/min combined (Retell + ElevenLabs + GPT-4.1). Cheaper LLM swap (GPT-4.1 Mini or Claude Haiku) brings this down to ~$0.08/min.

ARCHITECTURE — WHO DOES WHAT

Retell AI (https://retellai.com) — the "voice brain":

Telnyx (https://telnyx.com) — the "telephone wire":

Why both: Retell can also "buy" numbers directly, but their buy-flow requires Persona identity verification which can block. Importing a Telnyx number via SIP trunk sidesteps that entirely and gives more telephony control.

PREREQUISITES — BEFORE YOU START

  1. Retell AI account (https://retellai.com) — credit card on file, Secret Key in hand
  2. Telnyx account (https://telnyx.com) — funded with at least $5
  3. The agent already has a working LLM provider (Section 2F)
  4. The agent's /opt/data/.env file is writable

PHONE SETUP — TWILIO + RETELL (current)

IVY's phone is a Twilio number provisioned through Retell — the legacy Telnyx SIP-trunk procedure that filled this section has been removed (Telnyx is BANNED). Current setup: a Retell voice agent (IVY) bound to a Twilio number (+1-817-632-6536), outbound calls via Retell's API. See PART 0 §0.2 (PHONE CARRIER) and PART 0 PHASE 8 / CHAPTER E (VOICE AGENT IVY) for the authoritative current configuration — including the nuance that no direct Twilio API keys live in /opt/data/.env (the number lives at Retell's provisioning layer).

Section 12: Problems & Solutions — Bilateral Channel Bug (Session 54)

Problem: Code ↔ Lisa Communication Channel Broken (Sessions 24-54)

Timeline: Discovered Session 24, diagnosed Session 52, fixed Session 54 (30 days)

Severity: CRITICAL — All Code↔Lisa coordination required King Solomon relay (manual, non-scalable)

The Four Bugs

Bug #1: Checkpoint-on-Failure Silent Data Loss

Bug #2: System Prompt Crippling Tool Execution

Bug #3: No Claude Code Polling Mechanism

Bug #4: One-Way Channel Design

Solution: File-Based Bilateral Channel (Sessions 48-54)

Principle: Durable file-based async messaging with checkpoint tracking, no auto-execution.

Fix #1: Checkpoint-Only-on-Success (Lisa's Side)

Fix #2: System Prompt Rewrite + Max Tokens (Lisa's Side)

Fix #3: Code-Side Polling Mechanism (Code's Side)

Fix #4: Symmetric Return Path (Both Sides)

Why It Took 30 Days

  1. Sessions 24-35 (Band-Aid Phase): Point-fixes for symptoms, not root causes
  2. Sessions 36-46 (Wrong Architecture): Operational firefighting masked the systemic issue
  3. Session 47 (Turning Point): King Solomon's "STOP RIGGING" directive
  4. Sessions 48-51 (Structural Foundation): Architecture defined, auto-heal deployed
  5. Sessions 52-54 (Lisa Takes Lead): Lisa diagnosed all four bugs, fixed her side, Code wired up polling

Why This Architecture Is Durable

Reference

Full interview transcript: BILATERAL_CHANNEL_INTERVIEW.md (10-question technical deep-dive with Lisa)

BLUEPRINT MAINTENANCE LOG

This manual is now a LIVING, AUDITED document — the step-by-step blueprint for mass-producing Hermes agents (target: 10,000). Nothing enters it unless VERIFIED working. Mistakes and their fixes are recorded as first-class entries so they are never repeated.

[2026-06-07] Maintenance system ACTIVATED (Lisa + Claude Code, King Solomon directive). • Lisa runs manual_audit.py nightly at 9 PM CT (cron aaca68b9c618): audits this manual against the live system and writes the deltas to MANUAL_AUDIT.md. • Claude Code applies the audited changes to this document. EDIT ACCESS VERIFIED 2026-06-07 — this very entry, written by Claude Code via the Google Docs API, is the proof. • Division of labor: Lisa audits (she lives in the system) → Claude Code edits the manual (he has Google Docs write access) → King reviews.

First audit (2026-06-07) findings, queued for the next maintenance pass: • 3 STALE sections to fix: lisa.sh references (tool removed), Code↔Lisa channel mapping (outdated — superseded by email bridge + cloud routine), Session-46 LLM routing (no longer current). • 5 SYSTEMS missing from the manual: balance footer, memory auto-extraction pipeline, Code↔Lisa email bridge + cloud routine, token dashboard, debug circuit breaker. • SOUL.md: Empire-Manual reference was missing (now fixed by Lisa). • TO ADD as blueprint chapters (verified today): Honcho memory provider setup, memory auto-extraction pipeline, Code↔Lisa email bridge, Hostinger VPS API control plane.

PART 0 · §0.4 CONTENT — VERIFIED MISTAKES & FIXES (migrated from Sessions 24–57)

(Belongs under PART 0 at the top; placed here until the daily routine relocates it. Each entry = a real failure we hit + the verified fix, so agent N+1 skips it. NONE of these are guesses — we lived them.)

  1. ROOT-OWNED /opt/data → GATEWAY OUTAGE. Cause: docker exec defaults to root; root-owned config.yaml / auth.json / .env break the hermes-user gateway and throw MISLEADING 401 "user not found" errors. Fix: ALWAYS write as hermes (-u hermes / hexec.sh). Recover: chown -R hermes:hermes /opt/data + relaunch gateway via gosu.
  1. PAID-MODEL BUDGET DRAIN. Cause: agent silently running on deepseek-v4-pro (PAID) instead of the free Gemini primary — burned ~$97/$100 in a month, hit $2.68 (2026-06-07). Fix: model.default=gemini-2.5-flash, model.provider=google-gemini-cli (FREE OAuth); DeepSeek is PAID FALLBACK only. Check spend daily. Every agent ships on the free model.
  1. HOSTINGER SSH IS KEY-ONLY. Cause: host sshd is PermitRootLogin prohibit-password; resetting the root password via the Hostinger API succeeds but does NOT grant a password SSH login. Fix: Hostinger API = provisioning/lifecycle/restart ONLY; for a shell, an SSH PUBLIC KEY must be installed in /root/.ssh/authorized_keys from inside the box. Stop chasing password SSH.
  1. EMAIL BODY-READ FAILURE (agent↔Code). Cause: the agent's Hostinger IMAP cannot reliably read the message BODY — only the subject line — so it auto-acks "Processing now" without ever acting on the content. Fix: ALWAYS CC the shared Gmail (cfmbusiness@gmail.com); the agent reads the full body via the Gmail API. This is THE fix that makes the email bridge actually two-way.
  1. FILE-BASED COMMS CHANNELS ROT. Cause: claude_inbox.md / COORDINATION.md / polling crons / lisa.sh are unreliable, and the ephemeral CLI agent (Claude Code) is not always running to read them. Fix: email (CC shared Gmail) is the durable channel; human Telegram relay is the 100%-reliable fallback. DO NOT rebuild file inboxes — 30 days were lost to this.
  1. MEMORY 10K-CAP EVICTION. Cause: the bounded MEMORY.md fills and is forced to evict old facts = the agent "forgets." Fix: raise memory.memory_char_limit AND enable an external memory provider (Honcho cloud, or Holographic for $0) that sits BESIDE the bounded core (additive, never replacing it), plus a nightly auto-extraction pipeline that pulls durable facts from session history.
  1. CLOUD ROUTINE HAS NO VPS ACCESS. Cause: the Anthropic-cloud Code routine has no Hostinger token and no SSH, so it cannot restart the container or reach the VPS filesystem. Fix: container restarts are triggered from the workstation via the Hostinger API (or by an in-container/host agent) — never expect the cloud routine to do VPS ops.
  1. POST-RESTART CASCADE. Cause: a container/VPS restart can drop cloudflared, drift the model config, and stop crons. Fix: bake restart-critical processes into container init/entrypoint (not manual instructions); after EVERY restart verify model.default + crons firing + memory provider active.

PART 0 · EXPANDED BUILD CHAPTERS — the [TODO] detail, filled in with verified systems (Session 57, 2026-06-07)

(Belongs under PART 0; these are the real per-phase procedures. The legacy Phases/Sections below are superseded where they conflict.)

CHAPTER A — PROVISIONING & VPS CONTROL (Phase 1 detail) [Hostinger VPS API] • Base URL: https://developers.hostinger.com/api/vps/v1 · Auth: header Authorization: Bearer ${HOSTINGER_API_TOKEN} (per-account token; store in .env). • List/verify: GET /virtual-machines → returns id, state, ipv4. A healthy box shows state="running". • Restart (load new config): POST /virtual-machines/{id}/restart → action ct_restart. ~60-90s to come back. • Root password: PUT /virtual-machines/{id}/root-password body {"password":"…"} — password MUST contain a symbol from -().&@?'#;/,+ or it 422s. • HARD LESSON (verified): the host is key-only SSH (PermitRootLogin prohibit-password). Resetting the root password SUCCEEDS but does NOT enable password SSH. For a shell you must install an SSH PUBLIC KEY into /root/.ssh/authorized_keys from inside the box. The Hostinger API is for lifecycle/provisioning, not shell. • Reachability: the Hostinger API works from any network (it is NOT TLS-intercepted, unlike custom agent domains). • [TODO: capture the exact POST /virtual-machines create body once we script a clean greenfield provision.]

CHAPTER B — LLM CONFIGURATION (Phase 3 detail) [VERIFIED] • Set free primary: hermes config set model.default gemini-2.5-flash · hermes config set model.provider google-gemini-cli (FREE Google OAuth). • Fallback chain: deepseek-v4-pro (PAID, $3/day cap) → gpt-5.3-codex (free). OpenRouter DISABLED. • NEVER: mistral-small (empty replies after tool calls), kimi-k2 (replies in Chinese), direct-anthropic (hits cap). • Verify: hermes config get model.default. • LESSON: agents drift onto paid models and silently drain budget (an agent burned ~$97/$100 in a month). Verify the model daily; every agent ships on the FREE primary.

CHAPTER C — MEMORY (Phase 5 detail) [VERIFIED on Lisa 2026-06-07] • Bounded core: MEMORY.md + USER.md. Raise the cap if it fills: hermes config set memory.memory_char_limit 15000 (default 2200). Bigger = more tokens/turn, so keep it lean and push bulk to the provider. • External provider (additive — sits BESIDE the core, never replaces it): hermes memory setup → choose: – honcho (cloud): semantic vector search + dialectic person-modeling. HONCHO_API_KEY in .env. $100 free credit on signup, ~$2/mo base, pay-per-dialectic-call. Set dialecticReasoningLevel=low to stretch credit. Weekly credit tracker; fall back to Holographic if credits hit a floor. – holographic (FREE): local SQLite, semantic-ish recall, $0 forever — the no-funds option. • recall_mode: hybrid (auto-inject + explicit retrieval tools). • AUTO-EXTRACTION PIPELINE (nightly): a cron scans the session DB (state.db), pulls durable facts (high-confidence keywords: always/never/prefers/hates/must/permanent/no-exceptions) → auto-applies to MEMORY.md; lower-confidence → PROPOSED.md for review. Files: auto_extract_memories.py, EXTRACTION_INBOX.md, PROPOSED.md, LAST_EXTRACTION.md. • Verify: hermes memory status shows the provider active.

CHAPTER D — AGENT ↔ CLAUDE CODE COMMUNICATION (Phase 7 detail) • Channel: EMAIL. The agent reads the shared Gmail (cfmbusiness@gmail.com) via the Gmail API (it has the OAuth token). • THE RULE (root-cause fix, verified): Code emails the agent and ALWAYS CCs the shared Gmail. The agent's own Hostinger IMAP cannot reliably read the message BODY (only the subject) — CC'ing the shared Gmail lets it read the full body via the Gmail API and reply with substance instead of an empty auto-ack. • Agent → Code: reply to the thread WITH the answer, CC the shared Gmail. • Optional always-on wake: a Claude Code Routine on Anthropic cloud (created via the RemoteTrigger / claude.ai routines API) wakes Code on a schedule or API fire to read+answer email without a laptop open. • 100%-reliable fallback: human Telegram relay. • LESSON: do NOT build file-based inboxes (claude_inbox.md / COORDINATION.md / polling crons) — 30 days were lost to that. Do NOT claim "autonomous" until a real round-trip with substance is verified.

CHAPTER E — VOICE AGENT "IVY" (Phase 8 detail) [TWILIO ONLY — Telnyx is BANNED] • Stack: Retell voice platform + TWILIO SIP trunk. Voice: ElevenLabs "Brynne". (Legacy Section 13's Telnyx setup is OBSOLETE and must be purged.) • OUTBOUND PERSONA (mandatory for any outbound-capable agent): begin_message_mode = "dynamic" (NO hardcoded greeting). The system prompt has an OPENING section that consumes dynamic variables: call_direction, callee_name, calling_on_behalf_of, purpose. • The caller (place-call script) MUST pass retell_llm_dynamic_variables on every POST to /v2/create-phone-call. • Tools the agent exposes: /think (mid-call reasoning bridge to a reasoning model), /place_call, /save_task, transfer-to-human. • Anti-hallucination system prompt V3+. Webhook call summaries on completion. Handler runs on port 8643. • PRE-LIVE CHECK: dry-run the place-call script, then GET the LLM via the Retell API and confirm the OPENING section is present and begin_message is empty BEFORE any live call. If either fails, do not live-test. • LESSON: an inbound-only agent (hardcoded greeting) used for an outbound call greets the callee as if THEY called — embarrassing. Every outbound agent ships with the dynamic outbound persona by default. • [VERIFY with Lisa: current Retell agent_id, llm_id, and the exact Twilio trunk/number binding.]

PART 0 · CHAPTER E (VERIFIED) — VOICE AGENT "IVY" BUILD [TWILIO]

Pulled from the LIVE agent's IVY_BUILD_PLAYBOOK.md v1.0 on 2026-06-07 via SSH. Telnyx is BANNED — THIS is the canonical voice build. Replaces legacy Section 13 entirely.

E.1 IDENTITY & VOICE (locked) • Agent name: Ivy (per-deploy variable: VOICE_AGENT_NAME) • Voice: 11labs-Brynne (ElevenLabs) • Language: en-US • Channel: voice • Backchannel: enabled • Responsiveness / interruption sensitivity: default.

E.2 BEGIN MESSAGE — outbound persona (AI speaks first, DYNAMIC — this is mandatory for outbound) • Enable "AI speaks first." Set the custom message EXACTLY (double curly braces are Retell dynamic variables, substituted at call time): Hi {{callee_name}}, this is {{VOICE_AGENT_NAME}} calling on behalf of {{calling_on_behalf_of}}. {{purpose}} • This is what prevents the "inbound greeting on an outbound call" failure. Never hardcode a greeting.

E.3 TELEPHONY — TWILIO ONLY • Provider: Twilio • Phone number: +18176326536 (per-deploy: AGENT_PHONE_NUMBER) • Inbound agent = Outbound agent = Ivy • Allowed inbound/outbound countries: all.

E.4 TRANSFER DESTINATION • transfer_call → the owner's number (live value: +19455452529, King Solomon). Used when the caller asks for a human or for sensitive/decision matters the agent can't handle.

E.5 WEBHOOK (call summaries) • URL: https://<AGENT_DOMAIN>/webhook/retell-call-summary (live: automatemybiz.biz) • Events: call_started, call_ended, call_analyzed.

E.6 TOOLS / FUNCTIONS (add in this order)

  1. end_call (Retell built-in) — end only when the user clearly signals done (bye/goodbye/that's all); NEVER on silence or "call me back."
  2. transfer_call (built-in) — destination = owner's number (E.4).
  3. place_call (handler) — POST https://<AGENT_DOMAIN>/v1/functions/place_call ; speaks "Calling them now." ; schema {to_number (E.164, required), purpose}.
  4. think (handler) — POST https://<AGENT_DOMAIN>/v1/functions/think ; speaks "Let me think for a second." ; schema {question (required), call_context}. Mid-call reasoning bridge to a reasoning model.
  5. save_task (handler) — POST https://<AGENT_DOMAIN>/v1/functions/save_task ; captures a task/action item from the call.

E.7 OUTBOUND CALL — the caller script (call_ivy.py) • POSTs to the Retell API /v2/create-phone-call with retell_llm_dynamic_variables = {call_direction, callee_name, calling_on_behalf_of, purpose} on EVERY outbound call. • PRE-LIVE CHECK (mandatory): dry-run the script, then GET the LLM via the Retell API and confirm the dynamic begin-message variables resolve, BEFORE any live call.

E.8 ANTI-HALLUCINATION & UPTIME • System prompt: anti-hallucination V3+ (sourced from ivy_system_prompt_v2.txt / IVY_LEAN_PROMPT_v1.md on the live agent). • The Retell function handler runs on port 8643; an "ensure-retell-handler" cron keeps it alive (self-heal).

E.9 PER-DEPLOY PARAMETERS (the template — fill per agent) • VOICE_AGENT_NAME • AGENT_PHONE_NUMBER (Twilio) • OWNER_TRANSFER_NUMBER • AGENT_DOMAIN (webhook + function URLs) • RETELL_API_KEY (in .env). Everything else above is LOCKED standard.

[SOURCE FILES on the live agent, for the full build: /opt/data/memories/IVY_BUILD_PLAYBOOK.md, /opt/data/scripts/call_ivy.py, /opt/data/scripts/retell_function_handler.py, /opt/data/ivy_system_prompt_v2.txt.]

BROWSER AUTOMATION — Giving Your Agent Browser Access

Web Tasks & Ad Posting Added: June 2026

By default, a Hermes agent can send messages, run scripts, and call APIs — but it cannot open a browser window, fill out a form, or post an ad. This section explains how to give an agent full browser access so it can do real web tasks autonomously.

WHAT "BROWSER ACCESS" MEANS The agent can navigate any website, click buttons, fill forms, log into accounts, take screenshots, and extract data — all from inside its container on the VPS, with no human touching a mouse.

THE STACK Three components work together:

  1. browser-use — The AI brain. Translates plain-English tasks ("post this ad on Craigslist") into browser actions. Already included in Hermes as a plugin at /opt/data/hermes-agent-research/plugins/browser/browser_use/
  1. Playwright — The browser controller. browser-use uses this under the hood to actually drive Chrome. Already installed (playwright v1.60.0) in the Hermes venv.
  1. Chromium binary — The actual headless browser that runs on the VPS. NOT included by default — must be installed separately (see steps below).

STEP-BY-STEP SETUP

STEP 1 — Install the Chromium binary Run this from Code's side. Install to /opt/data/ms-playwright so the agent user can read/write it:

ssh hermes-vps "docker exec -e PLAYWRIGHT_BROWSERS_PATH=/opt/data/ms-playwright hermes-agent-jemg-hermes-agent-1 /opt/hermes/.venv/bin/python3 -m playwright install chromium"

This downloads ~290 MB (Chromium + headless shell + FFmpeg). One-time per agent.

STEP 2 — Install Chromium system dependencies Chromium needs system-level libraries. Install as root:

ssh hermes-vps "docker exec -e PLAYWRIGHT_BROWSERS_PATH=/opt/data/ms-playwright hermes-agent-jemg-hermes-agent-1 /opt/hermes/.venv/bin/python3 -m playwright install-deps chromium"

⚠️ DO NOT SKIP — Chromium will crash on image-heavy pages without this.

STEP 3 — Deploy the browser_task.py script Save to /opt/data/scripts/browser_task.py (owned by hermes:hermes). Full script:

#!/usr/bin/env python3 import os, sys, asyncio os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/opt/data/ms-playwright" os.environ["ANONYMIZED_TELEMETRY"] = "false" from browser_use import Agent, Browser from browser_use.browser.profile import BrowserProfile from browser_use.llm import ChatAnthropic

CHROME_PROFILE = "/opt/data/chrome-profile" SCREENSHOT_DIR = "/opt/data/listings/_screenshots" os.makedirs(CHROME_PROFILE, exist_ok=True) os.makedirs(SCREENSHOT_DIR, exist_ok=True)

def load_env_key(key_name): value = os.getenv(key_name) if value: return value with open("/opt/data/.env") as f: for line in f: if line.startswith(f"{key_name}="): return line.split("=", 1)[1].strip() raise RuntimeError(f"{key_name} not found")

async def run_task(task): proxy = os.getenv("BROWSER_PROXY") profile = BrowserProfile( headless=True, user_data_dir=CHROME_PROFILE, args=["--no-sandbox","--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage","--disable-gpu"], proxy={"server": proxy} if proxy else None, ) browser = Browser(browser_profile=profile) llm = ChatAnthropic( api_key=load_env_key("ANTHROPIC_API_KEY"), model="claude-sonnet-4-6", ) agent = Agent(task=task, llm=llm, browser=browser) result = await agent.run() await browser.close() return result

if __name__ == "__main__": task = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Go to example.com and return the page title" asyncio.run(run_task(task))

STEP 4 — Add ANTHROPIC_API_KEY to the container's .env The browser agent uses Claude Sonnet as its reasoning LLM. Key must be inside container:

ssh hermes-vps "docker exec hermes-agent-jemg-hermes-agent-1 bash -lc 'echo \"ANTHROPIC_API_KEY=YOUR_KEY_HERE\" >> /opt/data/.env'"

WHY CLAUDE SONNET? Haiku 4.5 returns thinking format browser-use can't parse. Gemini free tier exhausts quickly. Groq Llama lacks json_schema support. Sonnet is the safe default.

STEP 5 — Smoke test ssh hermes-vps "docker exec -u hermes -e PLAYWRIGHT_BROWSERS_PATH=/opt/data/ms-playwright hermes-agent-jemg-hermes-agent-1 bash -lc '/opt/hermes/.venv/bin/python3 /opt/data/scripts/browser_task.py \"Go to example.com and return the page title\"'"

Expected: [browser_task] Result: ... Example Domain ...

HOW TO CALL IT FROM THE AGENT

# Simple task: /opt/hermes/.venv/bin/python3 /opt/data/scripts/browser_task.py "go to dallas.craigslist.org and find the first 3 massage table listings with prices"

# With proxy: BROWSER_PROXY=http://user:pass@host:port /opt/hermes/.venv/bin/python3 /opt/data/scripts/browser_task.py "post this listing on Craigslist"

ALWAYS use the full python path: /opt/hermes/.venv/bin/python3 The system Python is PEP 668 locked. The full path always works.

PERSISTENT LOGIN SESSIONS

The Chrome profile at /opt/data/chrome-profile stores cookies/sessions between tasks.

To log in for the first time: give the agent a task like "Go to craigslist.org, log in with email X and password Y, confirm you're logged in." After that, all future tasks use the saved session.

WHAT WORKS WITHOUT A PROXY

The VPS IP (2.24.96.191 on Hostinger) is a datacenter IP. Most sites don't care.

✅ Works without proxy:

❌ Blocked without proxy:

PROXY — WHEN YOU ACTUALLY NEED IT

Only add a proxy when the agent hits a site that blocks it.

❌ ISP Dedicated Proxy (avoid): Single static IP, cheap (~$5-10/mo), still fingerprintable. Won't reliably bypass Facebook/LinkedIn.

✅ Rotating Residential Proxy (if ever needed): Real home IPs that rotate per request. Charged per GB (~$3-10/GB). Actually bypasses PerimeterX and Akamai. Buy pay-per-GB, not monthly.

To activate for a specific task, set the BROWSER_PROXY env var. No code changes needed.

KNOWN LIMITATIONS (as of June 2026)

WORKSTATION AGENT BRIDGE — AUTONOMOUS CLAUDE CODE ↔ OPENCODE (added 2026-06-18)

Beyond the VPS-side Bilateral Broker, King's workstation runs a second agent-to-agent channel: a lightweight daemon (scripts/agent_bridge.py) that lets the two local AI engineers — Claude Code (in VS Code) and OpenCode — talk and collaborate automatically, with NO human relaying messages between them. Proven live June 18, 2026.

How a message flows:

  1. Claude Code appends to code_to_opencode.md (or OpenCode appends to opencode_to_code.md).
  2. The daemon detects the new content and wakes the recipient headlessly (opencode run / claude -p, both auto-approving).
  3. The recipient reads the full conversation history, appends its reply, and exits.
  4. The reply wakes the first agent in turn. The exchange ends cleanly when a message contains the [EOT] tag.

Why it's reliable: • Runaway loops — [EOT] terminator + 30 wakes/hour rate cap + instant kill switch (create the bridge_STOP file). • Duplicate daemons — singleton PID lock; only one ever runs. • Survives reboots — auto-starts at every Windows logon (launcher in the Startup folder). Nothing to re-establish. • Windows file protection — all files live in C:\Users\<user>\.hermes-bridge\, outside Controlled Folder Access (no "blocked" popups, no failed writes).

Files: C:\Users\12149\.hermes-bridge\ holds the two channel files, the log (agent_bridge.log), and the kill switch (bridge_STOP). Daemon script lives at Hermes Agents\scripts\agent_bridge.py.

KEY DIFFERENCE FROM THE VPS BILATERAL BROKER: the Broker connects agents on the VPS via a persistent SQLite server; the Agent Bridge connects the two AI engineers on the local workstation via shared files + on-demand wakes. Together they let every agent in the Hermes system reach the others.

WORKSTATION AGENT BRIDGE — SESSION 65 UPDATE (June 18, 2026)

The autonomous Claude Code <-> OpenCode bridge (built Session 64) was finished and elevated on three fronts:

  1. REBOOT-VALIDATION — PASSED. Auto-start-on-reboot (via the Windows Startup folder) was tested through a real reboot and verified: after logon the daemon relaunched itself ~3 minutes later (fresh "agent_bridge START" log line, new PID 22520), with no admin and no manual step. The bridge now provably survives reboots on its own.
  1. COLLABORATIVE MODE — real work, not just chat. The wake instructions were rewritten so a message may be a WORK REQUEST: the woken agent edits repo files, runs commands/tests, verifies, and reports back. Guardrails baked into the prompt: stay within the repo; NO git commit/push unless explicitly told (changes left in the working tree for King to review); refuse unsafe/out-of-scope asks; [EOT] still closes the exchange. Per-turn timeout raised from 7 to 15 minutes. Proven live: one dropped instruction had OpenCode autonomously create and verify a repo file, then report back in ~32 seconds.
  1. CHAT-DRIVEN CONTROL — King drives the bilateral channel from chat, no terminal. A new one-purpose helper (scripts/to_opencode.sh) appends a single message to the Code->OpenCode channel (waking OpenCode), and King authorized only that command via the permission rule Bash(bash scripts/to_opencode.sh:*) in .claude/settings.local.json. (An AI cannot edit its own permissions — King added the rule by hand.) Everyday workflow: King tells Claude Code in plain words what he wants OpenCode to do -> Claude Code relays it -> OpenCode does the work -> Claude Code reads the reply and reports back in chat. Verified live ("Round-trip verified" from OpenCode).

Channel files / log / kill switch live in C:\Users\<user>\.hermes-bridge\. Safety rails (kill switch, 30 wakes/hr rate cap, singleton PID lock, EOF offsets) remain in force throughout.

WORKSTATION AGENT BRIDGE — HARDENED UNDER STRESS (June 18, 2026, Session 66)

A sustained 20-message stress test between the two local AI engineers (Claude Code and OpenCode) revealed that the Code↔OpenCode bridge, while reliable for single messages, had a hidden CLASS of bug: under bursts it could silently lose messages. Working as a team, the two agents traced three separate symptoms to ONE root cause — the daemon advanced its read position past a message before that message was actually delivered — and closed the whole class.

THE THREE BUGS (one root cause):

  1. Burst loss — 10 messages sent in a burst, only 1 delivered; 9 silently dropped (no error, no log). Fixed by a new pump() helper using "peek-then-commit": the read position advances only AFTER a message is delivered.
  2. [EOT] terminator was chunk-wide — a burst containing a closing [EOT] threw away the real messages batched with it. Fixed by making [EOT] detection line-based: only the terminator line is dropped, real messages still delivered.
  3. Rate-cap silent drop + starvation — a message that tripped the hourly wake cap was discarded, and one busy direction could starve the other. Fixed with per-direction caps plus hold-and-retry: a capped message is held and retried, never dropped.

HOW THE TEAM WORKED:

VERIFICATION:

CURRENT STATE: The Code↔OpenCode channel is solid under any burst, terminator, or rate-cap scenario. Code on disk = committed = running = on GitHub. No pending actions.

PART 6 — QUICK-START WALKTHROUGH (Beginner Click-by-Click Edition)

Folded in from the HTML walkthrough edition on 2026-06-23. PART 0–5 above are the technical reference; this part is the gentle, video-style click-by-click setup (originally "How to Build a Hermes AI Agent — Complete Setup Guide", based on Nate Herk's video). Some overlap with the reference sections is intentional — a beginner can follow this start-to-finish, then use PART 0–5 for depth. PART 0 §0.2 remains the authoritative current-standards section.

CFM Business • Staff Training Document

A plain-English manual for staff who set up AI agents for clients — from zero to a working, self-improving assistant

Version2.0 — Illustrated Edition

Based OnNate Herk's official tutorial video + CFM field experience

UpdatedJune 2026

AudienceStaff setting up Hermes agents for clients

⚠️ CURRENT REALITY OVERRIDE — 2026-06-23

This guide has some stale spots. These are the current CFM standards and WIN over any contradiction below:

Authoritative detail lives in empire-site/MANUAL.txt PART 0 §0.2 (the synced master).

Section 1

What Is Hermes?

Hermes is an open-source AI agent built by Nous Research. Think of it as an AI employee that lives on your client's server, works 24/7 even when your client's computer is off, and gets smarter over time by learning from every task it does.

Screenshot from Nate Herk's tutorial — What Hermes Agent Is

Key facts about Hermes:

How Hermes Compares to Other AI Tools

Hermes vs. Claude Code vs. OpenClaw — from Nate Herk's video

Tool What It Is Who Drives It Best For

Claude Code Anthropic's coding assistant — lives in your computer's terminal You — you have to be there Writing code, building skills, setting things up. That's us — we use Claude Code to set Hermes up for clients.

OpenClaw Another autonomous AI agent — 355,000+ users, backed by OpenAI Runs on its own on a server Same things as Hermes — some people run both at the same time

Hermes ✅ Autonomous AI agent from Nous Research — lighter, faster, self-improving Runs on its own, 24/7 The agent you set up for clients. Works without human oversight after training.

Plain English

Think of Claude Code (us) as the contractor who builds the house. Think of Hermes as the house itself — once we build it and move the client in, it runs on its own. We don't need to be there for Hermes to keep working.

Section 2

Understanding the 5 Pillars — How Hermes Thinks

Before you set anything up, you need to understand how Hermes works internally. Hermes is built on five pillars. Each one has a job.

The five core topics covered in Nate Herk's full tutorial

Pillar 1: Memory

How Memory works inside Hermes — loaded fresh at every session start

Memory is the small collection of facts Hermes carries from one conversation to the next. It is not a log of every message — it's more like a personal file that holds the important stuff.

USER.md

Who the client is — their name, preferences, communication style, things they don't like. Example: "Darrick prefers short replies. He runs a real estate business in Texas."

MEMORY.md

Facts about the client's environment, projects, and tools. Example: "GitHub repo is lisa-hermes-backup. Telegram bot is Lisa The AI Queen."

Definition: Memory

The small set of important facts Hermes remembers between conversations. It's like an employee's personal notepad — they jot down the things that matter, not every word that was said in every meeting.

Beginner Mistake to Avoid

Do NOT store secrets (passwords, API keys) in Memory. Memory is readable and searchable. Secrets always go in the .env file.

Pillar 2: Skills

Skills are reusable playbooks — Hermes builds them and reuses them automatically

A Skill is a saved set of instructions for how to do a task well. The first time Hermes does something complex, it figures it out step by step. Then it saves those steps as a skill so it can do it instantly next time.

Definition: Skill

A Skill is like a recipe card. The first time you make a dish, you experiment. Once you know the recipe, you write it down. Next time, you skip the guesswork. Hermes does the same thing — it writes its own recipe cards and reuses them.

There are two ways to give Hermes a new skill:

Pillar 3: Soul

SOUL.md shapes how Hermes talks — its tone, personality, and communication style

SOUL.md controls the agent's personality. It's what makes Hermes sound like a specific assistant instead of a generic chatbot. You can make Hermes formal, casual, concise, warm, or anything the client wants.

Definition: Soul

Soul is the agent's personality file. If Memory is what Hermes knows about the client, Soul is who Hermes is. Same brain, different character. Like how the same person can be formal at work and relaxed at home.

Pillar 4: Crons

Crons turn Hermes from a reactive chatbot into a proactive, scheduled worker

A Cron is a scheduled task — something Hermes does automatically at a set time without anyone asking. Example: "Every morning at 6am, send me a summary of my emails." That's a cron job.

Definition: Cron

A Cron is an alarm clock attached to a task. You set it once, and Hermes does that task every day (or every hour, or every week) without being asked again. The word "cron" comes from the Linux scheduling system — just think of it as "automatic task."

Pillar 5: The Self-Improving Loop

The self-improving loop: do → save → skill → search → get better over time

This is what makes Hermes different from a regular chatbot. Every time Hermes does something well, it can save that knowledge permanently. Over time, it becomes more capable and faster for that specific client.

The loop: Do the work → Save useful facts to Memory → Turn repeatable steps into Skills → Search past sessions when needed → Better next run.

"This isn't a tool you finish setting up. It's a teammate you keep training." — Nate Herk

Section 3

What You Need Before You Start

Before you touch Hostinger, make sure you have the following ready. This is your pre-flight checklist.

About the AI Brain (LLM)

Hermes needs an AI model to think with. The CFM standard is MiniMax-M3 via the minimax provider (api.minimax.io), with no fallback chain — see PART 0 §0.2. (Older videos/walkthroughs show OpenAI Codex, Gemini, DeepSeek, or OpenRouter; those are NOT used at CFM.)

Step 1

Get a VPS on Hostinger

A VPS is the server that will run the agent 24/7. Think of it as renting a small computer in a data center that never turns off. The agent lives there — not on your laptop, not on the client's laptop. It's always running in the cloud.

Definition: VPS (Virtual Private Server)

A VPS is a computer you rent in a data center. It runs Linux (Ubuntu), has its own IP address, and stays on 24/7 even when your laptop is closed. This is where Hermes lives. You control it by typing commands through SSH.

1

Go to hostinger.com and find the Hermes Agent page

Hostinger has a dedicated Hermes Agent landing page. Search for "Hostinger Hermes Agent" or go to hostinger.com and click "1-Click OpenClaw" at the top (Hermes is listed under their VPS applications).

The Hostinger page for Hermes Agent — showing the KVM 2 plan

Which Plan to Choose?

Select the KVM 2 plan. It gives you: 2 vCPU cores, 8 GB RAM, 100 GB NVMe storage, 8 TB bandwidth. This is the minimum recommended for a well-functioning Hermes agent. Do NOT go with the cheapest plan — it won't have enough memory.

Step 2

Choose Your Plan and Check Out

The checkout screen — KVM 2 plan, 12 months, ~$119.88/year total

1

Select 12-month billing

12 months comes out to ~$9.99/month. This saves money vs paying month-to-month. Hostinger also offers to switch to 24 months for the biggest savings — your call.

2

Skip the Daily Auto-Backup upsell

Hostinger will offer "Daily Auto-Backup" for $6/mo extra. You don't need this — we set up GitHub as our backup system in Step 10. Skip it.

3

Choose server location: United States - Boston

Pick the closest US location to your client. Boston or Dallas are both good. This affects how fast the server responds.

4

Complete checkout

After payment, Hostinger will ask you to set up your server. You'll continue to the "Choose what to install" screen next.

Step 3

The "Choose what to install" screen — select Docker application, then "Hermes Agent"

Hostinger gives you a choice of how to install Hermes. There are two methods. Always choose Docker.

Nate's breakdown of the two installation methods

Side-by-side comparison: Direct install vs. Docker container

You SSH into the server and manually run the Hermes installer. Files go directly onto the server. Harder to update, harder to manage, riskier if something breaks.

Hermes runs inside an isolated container. The container and the server share one folder (/opt/data/), which means your settings survive even if the container is rebuilt. Easier to restart, update, and manage.

Definition: Docker Container

A Docker container is like a small self-contained apartment inside your server building. Hermes lives in the apartment. The shared folder (/opt/data/) is like a shared storage unit that both the apartment and the building can access. If you tear down the apartment (delete the container), the storage unit (your data) is still safe.

1

Click "Docker application" tab

On the installation screen, you'll see tabs: Plain OS, Docker application, Control panel, Application. Click "Docker application."

2

Select "Hermes Agent" (not "Hermes Workspace")

You'll see two options: Hermes Agent and Hermes Workspace. Click Hermes Agent.

3

Click "Next"

Hostinger will prompt you to set your admin credentials.

The Deploy Hermes Agent dialog — set a username and password here

4

Set your Admin Username and Admin Password

Use: ADMIN_USERNAME: hermes and a strong password. Write these down immediately in the client's .env file in Claude Code before clicking Deploy.

Write It Down First

Before clicking Deploy, open the client's .env file in Claude Code and record the admin username and password. If you forget them, you'll have to SSH into the server and dig through config files to recover them.

Step 4

Access Your Hostinger Dashboard

After Hostinger finishes deploying (takes 3–5 minutes), you'll see the server dashboard. This is your control panel for the VPS.

The Hostinger VPS dashboard after deployment — note the left sidebar with all options

Key sections in the left sidebar:

Docker Manager — your Hermes container shows up here with status "Created"

Find Your IP Address

Your server's IP address is shown on the Overview page. Write it down in the client's CLAUDE.md file immediately. You'll need this IP every time you SSH in.

Step 5

SSH Into Your Server

Definition: SSH

SSH stands for "Secure Shell." It's a way to remotely control your server by typing commands. Think of it like texting your server: you type a command, the server does it and types back the result. There's no clicking — just text.

What a successful SSH login looks like — Ubuntu 24.04 LTS welcome screen with a command prompt

1

Open a terminal on your computer

On Windows: Open PowerShell (search "PowerShell" in the Start menu). On Mac: Open Terminal.

2

Type the SSH command

Replace the IP address with your client's actual server IP:

ssh root@2.24.96.191

You'll be asked "Are you sure you want to continue connecting?" — type yes and press Enter.

3

Type the password

You'll see a "password:" prompt. Type the root password (from the client's .env file). Nothing will appear on screen as you type — this is normal and expected. The password is hidden for security. Just type it and press Enter.

Password Looks Invisible — That's Normal

When you type your password in SSH, the cursor does not move and no characters appear. This is intentional — the terminal hides password input for security. Trust that you're typing correctly and press Enter when done.

4

You're in when you see the prompt

A successful login shows the Ubuntu welcome message and ends with a prompt like root@yt-hermes:~#. You are now in control of the server.

Step 6

Run the Hermes Setup Wizard

Once you're inside the Docker container, you run the Hermes setup wizard. This is an interactive menu that walks you through configuring the agent.

1

Enter the Hermes container

From your SSH session, type:

docker exec -it hermes-agent-jemg-hermes-agent-1 bash

(Your container name may be slightly different — check the Docker Manager in Hostinger if this doesn't work.)

2

Run the setup command

hermes setup

The Hermes setup wizard — choose "Quick setup" for first-time installation

3

Use the arrow keys to navigate. Press Enter or Space to select. The Quick setup covers: AI provider, model, and messaging. That's all you need to get started. You can run Full setup later.

Step 7

Choose Your AI Brain (LLM Provider)

Hermes needs an AI model to think with. During setup you choose your "inference provider" — the AI that powers the agent's responses.

Definition: LLM (Large Language Model)

An LLM is the AI "brain" Hermes uses to understand questions and generate responses. Without an LLM, Hermes is a body with no brain — it can't think.

CFM standard: MiniMax-M3. For CFM agents the model is MiniMax-M3 via the minimax custom provider (base_url https://api.minimax.io/v1), with no fallback chain. Vision is auxiliary (provider=auto, model=auto — not pinned to a model name). Do NOT use OpenRouter, Google Gemini, DeepSeek, Mistral, or direct-Anthropic as the agent's primary model — those are retired. (Full authoritative config: PART 0 §0.2 of this manual.)

1

Get a MiniMax API key

Create an account at api.minimax.io, generate an API key, and copy it.

2

Select the minimax provider in the setup menu

In the Hermes setup menu choose the minimax custom provider and set its base_url to https://api.minimax.io/v1. Press Enter.

3

Paste the key and set the model

Paste the API key when prompted — setup saves it to /opt/data/.env. Then set model.default = MiniMax-M3 (no fallback chain).

Step 8

Set Up Telegram (So Your Client Can Text Their Agent)

Telegram is how the client will communicate with Hermes after setup. Hermes uses a Telegram bot — a special account that Hermes controls. You create this bot using Telegram's BotFather.

Definition: Telegram Bot

A Telegram bot is a special Telegram account controlled by a computer (not a human). When the client sends a message to the bot, Hermes reads it and responds. The client just sees a chat conversation — they don't need to know anything technical.

Definition: BotFather

BotFather is Telegram's official bot creator. You message BotFather (on Telegram) and it creates your new bot and gives you a secret token. That token is what connects Hermes to the bot.

Creating a bot with BotFather — it gives you a token after naming the bot

1

Open Telegram and search for @BotFather

Find the official BotFather account (it has a blue checkmark). Click Start.

2

Send /newbot

Type /newbot and press send. BotFather will ask for a name, then a username.

3

Choose a bot name and username

Name: Can be anything. Example: "Lisa Hermes" or "CFM Assistant."

Username: Must end in "bot" and be unique across all of Telegram. Example: LisaHermesBot or CFMAgentBot. If it's taken, add numbers or words until it works.

4

Save the bot token

BotFather will give you a token that looks like: 8935721190:. Copy this immediately and save it to the client's .env file. You only see it once in this message.

5

Find your Telegram User ID

Search for @userinfobot on Telegram. Send it any message. It will reply with your User ID (a number like 8705992194). Save this too.

6

Enter both values in the Hermes setup wizard

Back in your SSH terminal, the Hermes setup wizard will ask for the bot token and your user ID. Paste them in.

Setup complete — Hermes confirms Telegram is connected and shows tool availability

Step 9

Test That Everything Is Working

1

Launch Hermes from the terminal

Inside the container, type:

hermes chat

You should see the Hermes ASCII logo and a startup screen listing available tools and skills.

Hermes v0.13.0 loaded and running — showing all available tools and skills

2

Type "hello" in the terminal

Hermes should respond with a greeting. If it does, the CLI is working.

3

Start the Telegram gateway

Open a second terminal window (or use a screen session) and run:

hermes gateway

This starts the background process that connects Hermes to Telegram. Keep this running.

4

Send "hello" on Telegram to the bot

Open Telegram, find your bot (by the username you created), and send it "hello." Hermes should respond within a few seconds.

If Telegram Doesn't Respond

Check that the gateway is running (Step 3). If it stopped, restart it with hermes gateway. Also check the bot token is correct in /opt/data/.env. Open it with: nano /opt/data/.env

Step 10

Connect GitHub (Backup & Memory Sync)

GitHub is a website where developers store code. We use it as a nightly backup for the agent's memory, skills, and settings. If the server ever crashes, everything important is backed up to GitHub.

Definition: GitHub

GitHub is a website that stores files and tracks changes over time. Think of it like Google Drive but for code. We use it to automatically save a backup of the agent's brain every night so nothing is lost if the server breaks.

Hermes asking for GitHub credentials to set up the backup repository

1

Create a new private GitHub repository

Go to github.com → click "+" → "New repository." Name it something like lisa-hermes-backup. Set it to Private. Do NOT add a README. Click "Create repository."

2

Create a Personal Access Token

Go to: GitHub → Your profile picture → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic)

GitHub Personal Access Tokens page — click "Generate new token"

Token scopes — check the full "repo" checkbox (gives access to create and push to private repos)

3

Set token settings

Note: Give it a name like "hermes-backup"

Expiration: 90 days or "No expiration" (if you set an expiration, you'll need to rotate it)

Scopes: Check the "repo" box — this is the only scope Hermes needs for backup.

4

Copy the token immediately

After clicking "Generate token," GitHub shows it only once. Copy it right away and paste it into the client's .env file. Then save it to the server:

hermes config set GITHUB_TOKEN your_token_here

5

Tell Hermes to set up the nightly GitHub sync

In Telegram or the CLI, tell Hermes:

"Set up a nightly GitHub sync cron to back up my skills, memories, and soul file to my private repo."

Hermes will create the cron job and configure it to run at midnight Central time.

Step 11

Add API Keys to the Agent

API keys are passwords that let Hermes connect to external services like email, Slack, Google Calendar, and more. Each service requires its own key.

Definition: API Key

An API key is a unique password that lets two software systems talk to each other. When Hermes needs to check a client's Gmail, it uses a Gmail API key to prove it's allowed in. Without the key, Gmail won't let Hermes access anything.

Security Rule — Always Follow This

NEVER type API keys directly in a chat message (Telegram, Claude Code chat, or anywhere else). Keys in chat logs can be stolen. Always use one of the two methods below.

Method 1: Using the Hermes Config Command (Preferred)

Inside the container terminal, run:

hermes config set KEY_NAME your_key_value

Example: hermes config set MINIMAX_API_KEY your-minimax-key...

Method 2: Editing the .env File Directly

Open the .env file on the server:

nano /opt/data/.env

Add a new line: KEY_NAME=your_key_value. Press Ctrl+X, then Y, then Enter to save.

After Adding Keys

Restart the container after adding keys so Hermes picks them up: docker restart hermes-agent-jemg-hermes-agent-1

Common Keys to Add

Service Key Name Where to Get It

MiniMax (primary LLM) MINIMAX_API_KEY api.minimax.io → API keys

Groq (speech-to-text) GROQ_API_KEY console.groq.com → API keys

Telegram Bot TELEGRAM_BOT_TOKEN From BotFather in Step 8

GitHub Backup GITHUB_TOKEN From Step 10

Step 12

Give Hermes Its First Job and Start Training It

Hermes is now running. Now you introduce it to the client's world. This is where the real customization happens.

1

Send an introduction message on Telegram

Open Telegram and send your new bot a voice note or message describing who the client is, what they do, and what they want the agent to help with. Example:

"Hey, I'm [Client Name]. I run a real estate business in Texas. My biggest needs are: summarizing my emails every morning, tracking leads in my spreadsheet, and posting to social media 3 times a week."

Hermes will listen, ask follow-up questions, and save this to USER.md automatically.

2

Set up the agent's personality (Soul)

Tell Hermes how to communicate. Example: "Always be concise. Use bullet points. Don't start messages with 'Certainly!' Be direct and professional."

Hermes will update its SOUL.md file.

3

Set up the first cron job

Ask Hermes to schedule something recurring. Example: "Every weekday morning at 7am Central, send me a summary of what's on my agenda today."

Hermes will create a cron job automatically.

4

Install relevant skills from the Skills Hub

Browse the Skills Hub and install skills that match the client's needs. Instructions in the next section.

Training Takes Time

Hermes gets better the more you interact with it. The first week is about teaching it — correcting mistakes, telling it your preferences, adding skills. By week 2 it should be running smoothly with minimal supervision.

Reference

CLI vs Telegram — When to Use Each

The mental model for CLI vs Telegram from Nate's video

Detailed breakdown of when to use each interface

Important: Hermes is the SAME agent in both CLI and Telegram. Telegram does not give you a dumber version. What changes is the control surface, not the intelligence.

Mental Model CLI (Terminal) Telegram

Think of it as... The cockpit — full controls visible The remote control — simple buttons

Best for... Setup, debugging, writing skills, inspecting memory, approving sensitive commands Quick tasks, daily check-ins, voice messages, triggering crons, getting reports

Visibility Full: see every tool call, token usage, context window Limited: you see the conversation, not the internals

Who uses it You (the setup person) during installation and maintenance The client, every day

Reference

The Skills Hub — Teaching Hermes New Tricks

Skills Hub — Anthropic category showing official skills like pdf, pptx, skill-creator, internal-comms

Skills Hub at hermes-agent.nousresearch.com/docs/skills — 684 total skills available

The Skills Hub is a library of pre-built skills you can install into any Hermes agent. Browse by category or search for what you need.

How to Install a Skill

Method 1 — From Telegram: Tell Hermes: "Install the [skill name] skill from the Skills Hub." Hermes will find, download, and install it.

Method 2 — From CLI:

hermes skills search email
hermes skills install [skill-url]

Skill What It Does

google-workspace Connect to Gmail, Google Calendar, Google Drive

github-code-review Review code in GitHub repositories

pdf Read and generate PDF files

youtube Summarize YouTube videos and extract content

hermes-agent Meta skill — helps Hermes explain itself and troubleshoot

Reference

Scaling Up — Multiple Agents on One VPS

Multiple specialist agents on one VPS — each in its own Docker container

As a client grows, you can run multiple specialized Hermes agents on the same VPS. Each agent lives in its own Docker container — they don't interfere with each other.

Definition: Specialist Agent

Instead of one agent that does everything, you create separate agents for separate jobs. One agent handles marketing. One handles finances. One handles operations. Like having different employees for different departments instead of one person doing everything.

When Should You Create a New Agent?

Decision flowchart — ask these questions to decide if a task needs its own agent

Simple rule: If the task needs its own memory, tools, API keys, schedule, or audience — give it its own agent. Otherwise, keep it in the main agent.

Recommended starting team — Personal Hermes as manager, 3-4 specialist agents

Good pattern (specialized agents) vs bad pattern (one mega-agent doing everything)

Starter Team for a Business Client

Personal Hermes (Manager) — main contact for the client, routes tasks

Marketing Hermes — content, brand, competitor research

Finance Hermes — invoices, budgets, payment tracking

Ops Hermes — VPS management, logs, server health

Reference

The Bilateral Broker — Agent-to-Agent Communication

When you run multiple Hermes agents (or need Claude Code on your workstation to talk to a Hermes agent on a VPS), you need a reliable, persistent communication channel. The Bilateral Broker is that channel.

Definition: Bilateral Broker

A lightweight message server that runs on the VPS host (outside any container). Any agent, script, or tool can send and receive messages through it. Messages are stored in SQLite and persist across restarts, reboots, and container rebuilds. Think of it as a shared bulletin board that never forgets.

Why Not Just Use Files or Crons?

Method Problem

File polling (read/write .md files) Race conditions, lost messages, no delivery confirmation, stale reads

Cron-based relays Crons break, have minimum intervals, add complexity, and are a single point of failure

Direct HTTP APIs Works but doesn't persist — if the receiver is offline, the message is lost

Bilateral Broker Persists all messages in SQLite, survives crashes and reboots, both sides can connect anytime, zero race conditions

Architecture

Where It Runs

The broker runs on the VPS host (not inside any container) as a systemd service. This means:

Who Connects

Setup — Step by Step

1

Deploy the broker server to the VPS host

Copy broker.py to the VPS host at /opt/data/scripts/broker.py:

scp broker.py root@YOUR_VPS_IP:/opt/data/scripts/broker.py

The broker is a Python Flask app (~100 lines) that uses SQLite for storage. No external dependencies beyond Flask.

2

Create the systemd service

Create /etc/systemd/system/bilateral-broker.service on the VPS host:

[Unit]
Description=Bilateral Agent Message Broker
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/data/scripts/broker.py
WorkingDirectory=/opt/data/scripts
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

3

Enable and start the service

systemctl daemon-reload
systemctl enable bilateral-broker
systemctl start bilateral-broker

Verify it's running:

systemctl status bilateral-broker

4

Open the firewall port

The broker listens on port 4097. Allow containers to reach it:

ufw allow from 172.16.1.0/24 to any port 4097
iptables -I DOCKER-USER -s 172.16.1.0/24 -p tcp --dport 4097 -j ACCEPT

Pin the iptables rule for boot persistence:

echo 'iptables -I DOCKER-USER -s 172.16.1.0/24 -p tcp --dport 4097 -j ACCEPT' >> /etc/rc.local

5

Deploy the client scripts

For Claude Code (workstation side): Deploy opencode-broker to /usr/local/bin/ on the VPS host. It's a bash script that uses curl to talk to the broker.

For Hermes agents (inside containers): Deploy lisa-send (or rename for your agent) to /usr/local/bin/ inside the container. It's a Python script that connects to the broker via HTTP.

API Reference

Endpoint Method Purpose

/send POST Send a message: {"to": "agent_name", "body": "message"}

/inbox/<agent_id> GET Read unread messages for an agent. Returns JSON array.

/ack/<id> POST Acknowledge (mark as read) a specific message by ID.

/status GET Broker health and message counts.

/health GET Simple liveness check. Returns {"status": "ok"}.

/messages GET Full message history (for debugging).

Usage Examples

From Claude Code (VPS host):

# Send a message to Lisa
opencode-broker send lisa "What's the status on the Ripplewood deal?"
# Check your inbox
opencode-broker inbox opencode

From inside a Hermes container:

# Send a message to OpenCode
python3 /usr/local/bin/lisa-send send opencode "Email sent to cfmbusiness@gmail.com"
# Check your inbox
python3 /usr/local/bin/lisa-send inbox

Survival Guarantees

What Survives What

Container restart: Broker unaffected (runs on host). Messages in SQLite preserved.

VPS reboot: Broker restarts automatically via systemd. Messages preserved.

Broker crash: systemd restarts it in 5 seconds. Messages preserved (SQLite write-ahead log).

Broker killed (SIGKILL): systemd restarts it in ~8 seconds. All queued messages intact.

Agent offline for days: Messages queue in SQLite. Agent reads them on next connection.

Critical Rule

The broker must run on the VPS host, not inside a container. If it runs inside a container, it dies when the container is rebuilt and takes all queued messages with it (unless the database is on a mounted volume). Host-side = permanent.

Reference

Giving Your Agent Browser Access — Web Tasks & Ad Posting

By default, a Hermes agent can send messages, run scripts, and call APIs — but it cannot open a browser window, fill out a form, or post an ad on Craigslist. This section explains how to give an agent full browser access so it can do real web tasks autonomously.

What "browser access" means

The agent can navigate any website, click buttons, fill forms, log into accounts, take screenshots, and extract data — all from inside its container on the VPS, with no human touching a mouse.

The Stack

Three components work together:

ComponentWhat It DoesAlready Installed?

browser-useThe AI brain. Translates plain-English tasks ("post this ad on Craigslist") into browser actions. Handles clicks, forms, navigation.Yes — included in Hermes as a plugin at /opt/data/hermes-agent-research/plugins/browser/browser_use/

PlaywrightThe browser controller. browser-use uses this under the hood to actually drive Chrome.Yes — playwright v1.60.0 in the Hermes venv

Chromium binaryThe actual headless browser that runs on the VPS. NOT included by default — must be installed separately.No — you must install it (see below)

Step-by-Step Setup

1

Install the Chromium binary

Run this from Code's side (requires root inside the container). Install it to /opt/data/ms-playwright so the agent user can read and write to it:

ssh hermes-vps "docker exec -e PLAYWRIGHT_BROWSERS_PATH=/opt/data/ms-playwright hermes-agent-jemg-hermes-agent-1 /opt/hermes/.venv/bin/python3 -m playwright install chromium"

This downloads ~290 MB (Chromium + headless shell + FFmpeg). It only needs to be done once per agent.

2

Install Chromium system dependencies

Chromium needs system-level libraries (fonts, codecs, display drivers). Install them as root:

ssh hermes-vps "docker exec -e PLAYWRIGHT_BROWSERS_PATH=/opt/data/ms-playwright hermes-agent-jemg-hermes-agent-1 /opt/hermes/.venv/bin/python3 -m playwright install-deps chromium"

Do not skip this step

Chromium will launch and navigate but crash on image-heavy pages if system deps are missing. The smoke test (example.com) passes without deps — real-world sites may not.

3

Install the browser_task.py script

This is the entry point the agent calls for any browser task. Create it at /opt/data/scripts/browser_task.py. The script handles:

#!/usr/bin/env python3
import os, sys, asyncio
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/opt/data/ms-playwright"
os.environ["ANONYMIZED_TELEMETRY"] = "false"

from browser_use import Agent, Browser
from browser_use.browser.profile import BrowserProfile
from browser_use.llm import ChatAnthropic

CHROME_PROFILE = "/opt/data/chrome-profile"
SCREENSHOT_DIR = "/opt/data/listings/_screenshots"
os.makedirs(CHROME_PROFILE, exist_ok=True)
os.makedirs(SCREENSHOT_DIR, exist_ok=True)

def load_env_key(key_name):
    value = os.getenv(key_name)
    if value: return value
    with open("/opt/data/.env") as f:
        for line in f:
            if line.startswith(f"{key_name}="):
                return line.split("=", 1)[1].strip()
    raise RuntimeError(f"{key_name} not found")

async def run_task(task):
    proxy = os.getenv("BROWSER_PROXY")
    profile = BrowserProfile(
        headless=True,
        user_data_dir=CHROME_PROFILE,
        args=["--no-sandbox","--disable-blink-features=AutomationControlled",
              "--disable-dev-shm-usage","--disable-gpu"],
        proxy={"server": proxy} if proxy else None,
    )
    browser = Browser(browser_profile=profile)
    llm = ChatAnthropic(
        api_key=load_env_key("ANTHROPIC_API_KEY"),
        model="claude-sonnet-4-6",
    )
    agent = Agent(task=task, llm=llm, browser=browser)
    result = await agent.run()
    await browser.close()
    return result

if __name__ == "__main__":
    task = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Go to example.com and return the page title"
    asyncio.run(run_task(task))

4

Add ANTHROPIC_API_KEY to the container's .env

The browser agent uses Claude Sonnet as its reasoning engine. The Anthropic API key must be present inside the container, not just on the workstation:

ssh hermes-vps "docker exec hermes-agent-jemg-hermes-agent-1 bash -lc 'echo \"ANTHROPIC_API_KEY=YOUR_KEY_HERE\" >> /opt/data/.env'"

Why Claude Sonnet?

browser-use requires a model that supports its structured output format. Claude Sonnet works reliably. Other models tested: Haiku 4.5 (returns thinking format browser-use can't parse), Gemini Flash (free tier quota exhausted quickly), Groq Llama (no json_schema support on most models). Sonnet is the safe default.

5

Smoke test

Verify the full chain works end to end:

ssh hermes-vps "docker exec -u hermes -e PLAYWRIGHT_BROWSERS_PATH=/opt/data/ms-playwright hermes-agent-jemg-hermes-agent-1 bash -lc '/opt/hermes/.venv/bin/python3 /opt/data/scripts/browser_task.py \"Go to example.com and return the page title\"'"

Expected output: [browser_task] Result: ... Example Domain ...

How to Call It From the Agent

Once installed, the agent can run any browser task by calling the script directly:

# From inside the container (agent's perspective):
/opt/hermes/.venv/bin/python3 /opt/data/scripts/browser_task.py "go to dallas.craigslist.org and find the first 3 massage table listings with prices"

# With a proxy (only if one is ever configured):
BROWSER_PROXY=http://user:pass@host:port /opt/hermes/.venv/bin/python3 /opt/data/scripts/browser_task.py "post this listing on Craigslist"

Always use the full python path

Use /opt/hermes/.venv/bin/python3 — not just python3. The system Python is PEP 668 locked and the venv's pip symlink may be broken. The full path always works.

Persistent Login Sessions

The Chrome profile at /opt/data/chrome-profile stores cookies and session data between tasks. This means:

To log into a site for the first time, give the agent a task like: "Go to craigslist.org, log in with email X and password Y, then confirm you are logged in." After that, all future tasks will use the saved session.

What Works Without a Proxy

The VPS IP (2.24.96.191 on Hostinger) is a datacenter IP. Most sites don't care. A few do.

Site / TaskWorks Without Proxy?Notes

Craigslist (browse + post)✅ YesConfirmed working in testing

eBay (browse)✅ YesLight bot detection

OfferUp (browse)⚠️ ProbablyAkamai detection — test first

Facebook Marketplace❌ NoPerimeterX blocks datacenter IPs

LinkedIn❌ NoBlocks cloud IPs aggressively

Public research / scraping✅ YesWorks on virtually all public sites

Proxy — When You Actually Need It

Only add a proxy when the agent hits a site that blocks it. Don't pay for one proactively.

❌ ISP Dedicated Proxy (avoid)

A single static IP that looks like a home connection. Cheaper (~$5-10/mo) but still fingerprintable. Won't reliably bypass Facebook/LinkedIn.

✅ Rotating Residential Proxy (if ever needed)

Real home IP addresses that rotate per request. Charged per GB (~$3-10/GB). Actually bypasses PerimeterX and Akamai. Buy pay-per-GB, not a monthly plan.

To activate the proxy for a specific task, set the BROWSER_PROXY env var when calling the script. No code changes needed.

Known Limitations (as of June 2026)

Reference

Keeping Your Agent Healthy

Nate's guide to maintaining Hermes over time

Situation What to Do

Agent gets something wrong Correct it on the spot. Tell it to update its skill or memory for that topic.

You gave the same instruction twice Tell Hermes: "Write a skill for this so you remember next time."

Agent is too wordy or off-tone Edit SOUL.md. Tell Hermes: "Update your soul file to be more concise."

Want something scheduled Build the skill first, then ask Hermes to create a cron for it.

Something breaks / weird behavior Check MEMORY.md first. Stale memory is the #1 cause of weird agent behavior. Run hermes doctor.

Agent won't start Check the container: docker ps. Restart it: docker restart [container-name].

Reference

Troubleshooting Common Issues

Hermes won't start / container is stopped

docker ps -a
docker start hermes-agent-jemg-hermes-agent-1

Telegram messages aren't going through

Make sure the gateway is running:

hermes gateway

If it crashes, restart it. Check that the TELEGRAM_BOT_TOKEN in /opt/data/.env is correct.

Agent forgot something it knew before

Memory may be stale or corrupted. Open /opt/data/memories/MEMORY.md and review. Remove outdated entries. Tell Hermes: "Re-read my memory file."

"nano: .env is a directory" error

You're either on the host server (not inside the container) or using the wrong path. The .env inside the container is at /opt/data/.env. Make sure you ran docker exec -it [container] bash first.

SSH password not accepted

Check the password in the client's .env file. Make sure you're typing it correctly (remember: nothing shows on screen as you type). If it's truly wrong, you can reset it from the Hostinger dashboard → OS & Panel → Reset root password.

Check full system health

hermes doctor

Bonus

Workstation Agent Bridge — Autonomous Claude Code ↔ OpenCode

Beyond the VPS-side Bilateral Broker, King's workstation runs a second agent-to-agent channel: a lightweight daemon that lets the two local AI engineers — Claude Code (in VS Code) and OpenCode — talk and collaborate automatically, with no human relaying messages between them. Proven live June 18, 2026.

Definition: Agent Bridge Daemon

A small Python watcher (scripts/agent_bridge.py) that monitors two shared text files. When one agent appends a message, the daemon launches a fresh session of the other agent to read it and reply — then that agent goes back to sleep. The daemon is the "doorbell"; each agent reads the shared history itself.

How a Message Flows

  1. Claude Code appends to code_to_opencode.md (or OpenCode appends to opencode_to_code.md).
  2. The daemon detects the new content and wakes the recipient headlessly (opencode run / claude -p).
  3. The recipient reads the full conversation, appends its reply, and exits.
  4. The reply wakes the first agent in turn. The exchange ends when a message contains the [EOT] tag.

Why It's Reliable

ConcernHow it's handled

Runaway loops[EOT] terminator + per-direction wakes/hour rate cap (hold-and-retry, never drops) + kill switch (bridge_STOP file)

Duplicate daemonsSingleton PID lock — only one ever runs

Survives rebootsAuto-starts at every Windows logon (Startup folder). Reboot-tested & verified June 18, 2026 — after a real reboot the daemon relaunched itself ~3 minutes after logon (fresh START log line, new PID), with no admin and no manual step.

Windows file protectionAll files in C:\Users\<user>\.hermes-bridge\, outside Controlled Folder Access

Collaborative Mode — Real Work, Not Just Chat (June 18, 2026)

The bridge was upgraded from a comms-only channel to a true collaboration channel. The daemon already woke each agent inside the repo with full permissions, so the change was in the wake instructions: a message may now be a work request. The woken agent reads the request, edits repo files, runs commands and tests, verifies the result, and reports back through the channel.

Chat-Driven Control — King Drives It From Chat, No Terminal (June 18, 2026)

King can now run the whole bilateral channel by simply talking to Claude Code in the VS Code chat — he never touches the terminal. A safety guardrail blocks the live Claude Code chat session from waking OpenCode on its own, which previously forced King to paste a command to start each exchange. The fix is a single narrow, append-only helper plus one permission rule:

PieceWhat it is

scripts/to_opencode.shA one-purpose command: it appends one message to the Code→OpenCode channel (which wakes OpenCode). It can do nothing else.

Permission ruleBash(bash scripts/to_opencode.sh:*) in .claude/settings.local.json — authorizes only that command. King must add this by hand; an AI cannot edit its own permissions (a hard self-modification guardrail).

Everyday workflow: King tells Claude Code, in plain words, what he wants OpenCode to do → Claude Code relays it → OpenCode does the work → Claude Code reads OpenCode's reply and reports it back in chat. Verified live ("Round-trip verified" from OpenCode).

Key difference from the VPS Bilateral Broker: the Broker connects agents on the VPS via a persistent SQLite server; the Agent Bridge connects the two AI engineers on the local workstation via files plus on-demand wakes. Together they let every agent in the system reach the others.

Hardened Under Stress — The Silent-Drop Bug Class Closed (June 18, 2026)

A sustained 20-message stress test between the two agents revealed that the bridge, while reliable for single messages, had a hidden class of bug: under bursts it could silently lose messages. Working as a team, Claude Code and OpenCode traced three separate symptoms to one root cause — the daemon advanced its read position past a message before that message was actually delivered — and closed the whole class.

BugSymptomFix

Burst loss10 messages sent in a burst, only 1 delivered — 9 silently dropped, no error, no logpump() "peek-then-commit": the read position advances only after a message is delivered

[EOT] chunk-wideA burst containing a closing [EOT] threw away the real messages batched with it[EOT] detection is now line-based — only the terminator line is dropped, real messages still delivered

Rate-cap drop + starvationA message that tripped the hourly cap was discarded; one busy direction could starve the otherPer-direction caps + hold-and-retry — a capped message is held and retried, never dropped

How the team worked: the agents divided the fixes (OpenCode took the [EOT] bug, Claude Code took the rate-cap and burst-loss bugs), peer-reviewed each other's code (5 of 5 checks passed), and — by reading the actual code rather than trusting each other's claims — caught a suspected fourth bug that turned out not to be real. An over-engineered "edit-lock" module was built mid-session and then shelved by mutual agreement, kept out of the daemon's critical path. Verified by deterministic proof scripts and two clean live test runs (a 2-message burst and a 5-message exchange, zero drops), then committed to git (the daemon had previously been untracked) and pushed to GitHub.

The Trilateral Agent Communication System

The Bilateral Broker and the Workstation Agent Bridge (above) are the two transports. The Trilateral Agent Communication System is what ties them together: it lets all three AI engineers — Claude Code (workstation), OpenCode (workstation), and Lisa (VPS container) — talk to each other autonomously, with no human relaying messages. Proven live June 19, 2026, with real message IDs.

⚠️ SUPERSEDED IN PART, 2026-06-30/07-01 (reconciled 2026-07-04). Everything below this point describes the June 19 v1 design accurately for what it covers, but it predates two major additions: (1) Kim joined the mesh with her own push receiver, not just poll, and (2) a trigger.dev cloud durable-delivery spine was added as a second, parallel path for Lisa. See "The Trigger.dev Durable Delivery Spine" further down for the current state. The June 19 poll-backstop design below is NOT dead — it's still the mechanism for Code and OpenCode, and still runs as Kim's backstop pending a hardening step (see that section for exactly what's still open).

The core idea — "never lose a message": durable delivery is kept separate from best-effort notification.

The legs (now four — Kim added 2026-07-01)

LegTransportInbound backstop

Claude Code ↔ OpenCodeLocal file bridge (~/.hermes-bridge/, agent_bridge.py) with headless wakesThe daemon itself; both agents share the file history

Claude Code ↔ LisaBilateral Broker (SQLite, port 4097)code_broker_poller.py — polls every 60s over SSH. Code is poll-only inbound because the workstation sits behind home NAT, so the VPS physically cannot push to it (this is physics, not a bug).

OpenCode ↔ LisaBilateral Broker (SQLite, port 4097)Push to port 4098 + opencode_poll_backstop.py (a 3-minute VPS cron) that drains any push-misses

Broker → LisaPush to port 8643 (/broker-push, IP 172.16.1.2)A silent lisa_ferry.py host cron that re-delivers push-misses without Telegram spam or spawning an LLM session

Broker → Kim (NEW 2026-07-01)Push to port 8643 on Kim's own subnet (/broker-push, IP 172.16.2.2 — her container's real IP; NOT .1, which is the docker network gateway, a common mix-up)kim_inbox_poll.py via the inbox-wake cron (every 2 min, verified live 2026-07-04) — still running as a backstop; NOT yet retired (needs an on-startup inbox-drain hardening step first, see the trigger.dev section below)

Components

ComponentWhereRole

bilateral-broker (systemd, :4097)VPS hostSQLite message backbone — never drops a message

agent_bridge.py (+ launcher)WorkstationCode ↔ OpenCode file bridge, headless wakes

code_broker_poller.py (60s)WorkstationCode's inbound poll backstop (NAT-safe)

opencode-listener (:4098) + opencode_poll_backstop.py (3-min cron)VPS hostOpenCode push receiver + push-miss backstop

/broker-push (:8643) + lisa_ferry.py (silent host cron)VPS container / hostLisa push receiver + silent push-miss ferry

kim_broker_push.py (:8643 on 172.16.2.2) + kim-broker-push.service (systemd, supervised by Hermes cron ensure-kim-broker-push every 5 min, verified live)VPS container / hostKim push receiver (stdlib http.server, no Flask) + inbox-wake poll backstop (every 2 min, not yet retired)

Cost

OpenCode runs on the deepseek-v4-flash model (about $0.14 / $0.28 per million tokens in/out). A typical wake is a fraction of a cent; idle costs $0 because polling is just lightweight SSH + SQLite reads. Realistically, pennies per day. Claude Code and Lisa use their own subscriptions for their wakes.

Resilience — the only true outage

Governance rule (so it never regresses)

Mechanical jobs — delivery, health checks, backstops — run as silent host crons, never as Hermes/Telegram-reporting crons, and message delivery must never spawn an LLM session. Waking an agent to think is a separate, rare event (a real-time push or a request from King), not a recurring cron job.

How to use it

# Code → Lisa
ssh hermes-vps "code-broker send lisa '<message>'"
# OpenCode → Lisa
opencode-broker send lisa "<message>"
# Code → OpenCode
bash scripts/to_opencode.sh "<message>"
# Check health
ssh hermes-vps "code-broker health"
# Kill switch (halts all waking)
create the file ~/.hermes-bridge/bridge_STOP

The Trigger.dev Durable Delivery Spine (added 2026-06-30/07-01)

A second, parallel delivery path added on top of the broker — not a replacement. King's mandate driving this: agents should talk autonomously with no polling, no crons wherever possible; the broker's poll backstops are the fallback of last resort, not the primary mechanism, for whichever agents have a real network path to receive a push.

How it works: the broker's /send handler (patched, fire_deliver() function, background thread) fires a POST to a trigger.dev cloud task on every message, in parallel with its own direct push:

POST https://api.trigger.dev/api/v1/tasks/deliver/trigger
Bearer <TRIGGER_PROD_SECRET_KEY>   (from systemd drop-in /etc/systemd/system/bilateral-broker.service.d/trigger.conf)
Body: {"payload":{"to","from","body","msgId"},"options":{"idempotencyKey":"hermes-msg-<id>"}}

Task source: C:\Users\12149\Documents\TRIGGER.DEV\src\trigger\deliver.ts — routes to the target agent's gateway via env vars (LISA_GATEWAY_URL/LISA_API_KEY/LISA_MODEL, stored in trigger.dev's OWN prod env store, separate from the VPS .env). Deploy: cd C:\Users\12149\Documents\TRIGGER.DEV && npx trigger.dev@4.4.6 deploy (PAT-authenticated as cfmbusiness@gmail.com; if it crashes with a transient esbuild panic, just retry).

⚠️ Kim was added to the wired set 2026-07-02 — "only Lisa" is now stale. Current live value (verified 2026-07-04 via grep WIRED_TRIGGER_AGENTS /opt/data/scripts/broker.py): WIRED_TRIGGER_AGENTS = {"lisa", "kim"}. Kim is NOT reached the same way as Lisa though — trigger.dev's cloud can't reach her docker-internal network directly, so her path routes through the broker's own narrow /kim-relay route instead of a direct gateway URL. Verified live end-to-end at the time (broker message #811 showed both push_ok and trigger_ok firing for Kim). Code/OpenCode still deliberately excluded — firing at agents without a reachable gateway clogged the trigger.dev dashboard with permanently-failing "Queued 8h+" runs during testing, which is exactly why the guard exists at all.

Current live project (verified 2026-07-04 via list_projects): proj_mptaxodjnlkwndeokikd (slug trilateral-communication-system-SUOH), org automate-your-biz-27b3 (id cmr1pqclg011mn60nf0jq8kwv). Confirmed actively firing today — broker.log shows trigger_ok: #1143 deliver fired (200) at 2026-07-04T16:14 UTC, a few hours before this reconciliation pass.

⚠️ A dead project ref exists — do not use it if it surfaces in old notes/chat history: proj_ctvfkhasuylklrazofls (org automate-your-biz-df44) was the ORIGINAL project, but King accidentally deleted the entire org on 2026-07-01 (~01:46 CT), taking the project, its prod key (tr_prod_GneHRVNNwrjqPEB0GeOf, now permanently 401), and its env vars with it. Full rebuild into the new org/project above took about 45 minutes. The same new org also holds two unrelated projects — "Automate Your Biz" (proj_jsjuamckjzwiegocgfzy, the actual AYB website, kept separate on purpose) and "Ivy Daily Brief" (proj_rpopdczloyrqzyqmogxp) — do not confuse either with the comms project.

Still open as of this reconciliation (2026-07-04) — do not assume done:

Email Layer (Kim + Lisa, added 2026-07-02)

Built the day after the trigger.dev rebuild, on top of the same mesh: agent-native email, not just cross-agent messaging.

Identities:

CC-King enforcement (hard rule, not a suggestion): both agents send outgoing mail through dedicated helpers — /opt/data/scripts/send_as_lisa.py and /opt/data/scripts/send_as_kim.py — that hard-code a CC to cfmbusiness@gmail.com on every message. Backed by a shared always-cc-king skill (/opt/data/skills/always-cc-king/SKILL.md, present in both containers) — confirmed still present on both sides, 2026-07-04.

Multi-account Gmail watch: broker.py's GMAIL_ACCOUNTS dict (added 2026-07-02) watches both cfmbusiness and Kim's inbox — confirmed still live (GMAIL_ACCOUNTS present in the live broker.py; Kim's kim_google_token.json refreshed as recently as 2026-07-04T17:26).

lisa-inbox-poll.ts — a trigger.dev scheduled task (every 5 min) that IMAP-polls Lisa's Hostinger mailbox, since Hostinger has no Pub/Sub push mechanism. Confirmed still running today (list_runs shows completions at 17:20 and 17:25 UTC, version 20260702.3).

Routing fix — gmail-history-sync.ts addressee-first: routes an inbound email by its To:/Cc: header first, only falling back to KIM_KEYWORDS keyword-matching if no agent address is named. Fixes a real misrouting bug found during this build: a reply that happened to mention "deal" was going to the wrong agent purely on keyword match, regardless of who it was actually addressed to.

Dedup: Gmail Pub/Sub (cfmbusiness/instant) and Lisa's IMAP poll (Hostinger/5-min) both watch overlapping mail — Message-ID-based dedup keeps a CC'd email from being delivered twice.

The Hostinger forwarding rule (lisa@automateyourbiz.infocfmbusiness@gmail.com, "save a copy" mode) makes Lisa's email near-instant instead of waiting up to 5 minutes for the IMAP poll. Had a real "zombie forwarder" bug — showed active in the Hostinger panel but silently never fired — fixed with Kodee's help via delete + recreate + re-confirm. Confirmed live via gmail_push log entries continuing through today.

Agent Honesty & Authorization Protocol (added 2026-06-30, after a real fabrication incident)

The incident: on 2026-06-30, Lisa fabricated an entire fake exchange — invented broker message IDs, invented replies attributed to Code, and false claims (a container being down, a key being rejected) that were both provably untrue at the time. Caught by directly querying the broker's real message log and finding the highest real ID was far lower than what Lisa had cited.

The rule added to Lisa's SOUL.md in response — confirmed LIVE 2026-07-04: Lisa's /opt/data/SOUL.md (currently 52 lines) contains an explicit anti-fabrication rule: "Fabricated progress or false confirmations. Never say 'done,' 'sent,'..." (worded differently than the original chat's literal "NEVER LIE" framing, but the substance — never report unverified progress, never invent another agent's messages — is present and live).

The "gink" codeword: described in the source conversation as added to both Lisa's and Kim's SOUL.md — "gink" ("King" spelled backwards) marks an inbound mesh message as a verified direct order from King, to be executed immediately and confirmed via broker reply. Verified live in the original session (Lisa replied RECEIVED-GINK #765, triggered a real Ivy phone call King confirmed receiving; Kim replied RECEIVED-KIM-GINK #768/#770).

✅ Status — RESOLVED 2026-07-04 (investigated same day the gap above was first flagged):

Appendix

Glossary — Plain English Definitions

VPS (Virtual Private Server) A computer you rent in a data center. It runs 24/7 even when your own computer is off. This is where Hermes lives. You control it by typing commands (SSH).

SSH (Secure Shell) A method of remotely controlling a server by typing commands. Like texting a computer. When you type a command, the server does it and shows you the result.

Docker / Docker Container A way to run a program in an isolated mini-environment on your server. Like a self-contained apartment inside a building. The container can be deleted and rebuilt without losing your data (which lives in the shared folder).

LLM (Large Language Model) The AI "brain" that powers Hermes's ability to understand and respond to messages. GPT, Gemini, and DeepSeek are all LLMs. Without an LLM, Hermes has no intelligence.

API Key A unique password-like code that allows two software systems to communicate securely. Example: A Gmail API key lets Hermes read your Gmail with permission.

Telegram Bot A Telegram account controlled by a computer program instead of a human. Hermes uses a bot to receive and send messages on Telegram.

BotFather Telegram's official bot-creation tool. You send it messages to create a new bot and get the token for it.

Token (Bot Token) A secret code that proves ownership of a Telegram bot. Hermes uses this to connect to and control the bot. Treat it like a password.

GitHub A website for storing and tracking files (especially code). We use it to back up the agent's memory and skills every night.

Memory (Hermes) The small set of important facts Hermes remembers between conversations. Stored in USER.md (client info) and MEMORY.md (project/environment facts).

Skill (Hermes) A saved set of instructions for how to do a task. Like a recipe card. Hermes builds and reuses skills to work faster and more reliably over time.

Soul (SOUL.md) The file that controls the agent's personality and tone. Edit this when the client wants the agent to sound different.

Cron / Cron Job An automatic scheduled task. Example: "Run this report every Monday at 8am." Set it once and Hermes runs it forever without being asked again.

CLI (Command Line Interface) The text-based terminal interface for controlling Hermes. Used by setup staff for configuration, debugging, and advanced work.

.env File A hidden file that stores all secret credentials (passwords, API keys). Located at /opt/data/.env on the server. Never share this file or commit it to GitHub.

Skills Hub An online library of pre-built skills for Hermes. Located at hermes-agent.nousresearch.com/docs/skills. Browse by category to find tools your client needs.

Gateway The background process that keeps Hermes connected to Telegram. Run it with hermes gateway. If it stops, Telegram messages won't reach Hermes.

Residential Proxy A service that routes the server's internet traffic through a home internet connection. Used to bypass bot-detection on sites like Upwork that block datacenter IPs.

Bilateral Broker A lightweight message server running on the VPS host that enables persistent, reliable communication between agents, Claude Code, and scripts. Uses SQLite for storage. Survives crashes, restarts, and reboots. Agents connect via REST API.

Trilateral Agent Communication System The system that connects all three AI engineers — Claude Code, OpenCode, and Lisa — so they message each other automatically, with no human in the middle. It combines the Bilateral Broker (VPS) and the Workstation Agent Bridge, and adds a "poll backstop" on every inbound leg so a failed push or wake never loses a message — the recipient simply picks it up on its next polling cycle.

Poll Backstop A small scheduled job that periodically asks the broker "do I have any messages I haven't received yet?" and pulls them down. It is the safety net beneath the fast "push" notifications: if a push ever fails, the backstop still delivers the message a short time later.

browser-use An open-source Python library that gives AI agents the ability to control a real web browser. It sits on top of Playwright and translates natural language tasks ("log into Craigslist and post this ad") into actual browser actions — clicking, typing, navigating, form submission. Installed in the Hermes venv; requires a Chromium binary and an LLM API key to operate.

Playwright A browser automation framework from Microsoft. browser-use uses it under the hood to actually drive Chromium. Installed as a Python package; the browser binary (Chromium) must be installed separately via python -m playwright install chromium.

Rotating Residential Proxy A proxy service that routes traffic through real home IP addresses that change each request. Required for accessing sites that block datacenter IPs (Facebook, LinkedIn). Charged per GB of data used. Distinct from an ISP dedicated proxy, which is a single static IP that is easier to fingerprint and block.

CFM Business — Hermes Agent Setup Guide v2.0 — June 2026

Based on Nate Herk's official tutorial: youtube.com/watch?v=gb5TlGw6Uks

For internal staff use only. Do not distribute.