{"slug": "hermes-architecture-a-10-minute-tour-as-of-2026-10-17", "title": "Hermes architecture: a 10-minute tour (as of 2026-10-17)", "summary": "A developer has published a specification for Hermes, a nightly Python agent that assembles a personalized \"serendipity dispatch\" of one discovery, one deep dive, one build idea and one markets note. The design keeps the LLM confined to selecting numbered indices from a pre-verified shortlist, so all fetching, URL verification and rendering stay deterministic and the model cannot hallucinate a link. The pipeline pulls from Hacker News, arXiv, GitHub Trending and RSS feeds, ranks candidates by keyword and recency, checks links before curation, and falls back to deterministic picks if the LLM call fails three times.", "body_md": "A single Python agent that runs nightly and produces a serendipity dispatch:\none surprising discovery, one deep dive, one build idea, and one markets note.\nNot morning news — things the owner wouldn't have found on their own.\n\nFeed this document to an AI assistant (Muse, etc.) and ask it to implement\nthe agent. Everything below is a specification, not code.\n\n1. What it does (pipeline)\n\n```\nDiscovery (no LLM) → interest-aware ranking → URL verification →\nLLM curation (picks by index) → deterministic rendering → dispatch + teaser\n```\n\nKey design rule: the LLM only selects (returns indices). All fetching,\nrendering, and link insertion is deterministic code. The model can never\nhallucinate a URL because it never writes one.\n\n2. Schedule\n\nRuns once nightly at 2:00 AM local time via cron.\n\nSingle-flight: use a lock file so overlapping runs are impossible.\n\nTimeout budget: ~15 minutes max (LLM retries are the long pole).\n\n3. Discovery sources (no LLM, pure HTTP)\n\nSource\n\nWhat to fetch\n\nHacker News\n\nTop stories via the official Firebase API (hacker-news.firebaseio.com)\n\narXiv\n\nRecent papers via the arXiv API, filtered to the owner's interest areas\n\nGitHub Trending\n\nScrape github.com/trending (daily)\n\nRSS feeds\n\nA configurable list of feeds (tech, science, finance); parse with stdlib xml.etree\n\nEach candidate is a dict: {title, url, source, summary, published_at}.\n\nRespectful fetching: declare a User-Agent identifying the bot. For SEC EDGAR\n(if used), the UA must contain an email-shaped contact or requests get 403.\n\n4. Interest-aware ranking (no LLM)\n\nMaintain a keyword list representing the owner's interests\n(e.g. AI agents, local LLMs, prediction markets, options, FPL, home automation).\n\nScore each candidate: keyword hits in title/summary, recency boost,\nsource diversity cap (max N per source so one source can't dominate).\n\nDedupe against a seen.json of URLs already dispatched (persist across runs).\n\n5. URL verification (no LLM)\n\nHEAD/GET each shortlisted URL with a short timeout (~8s).\n\nDrop dead links before they reach the LLM. A dispatch with a 404 is a failure.\n\n6. LLM curation\n\nSend the verified shortlist (numbered, with titles + summaries) to the LLM\nwith a prompt like:\n\n\"You are curating a nightly serendipity dispatch. Pick the 4 most\nsurprising, non-obvious items from the list below. Return ONLY the indices,\ne.g. 3, 7, 12, 18. One pick each for: a discovery (something new),\na deep dive (worth 20 minutes), a build idea (something to make),\na markets note (interesting prediction-market or options movement).\nSkip anything that reads like press-release hype.\"\n\nThe model returns indices only. Map them back to candidates in code.\n\nRetry the LLM call up to 3 times on failure; after that, fall back to the\ntop-ranked deterministic picks so the dispatch still ships.\n\n7. Markets note (deterministic)\n\nQuery the Kalshi public API (anonymous, read-only) for the biggest\nprice movers in the last 24h.\n\nRender the top 5 movers with before/after prices. No LLM needed,\nno trading, no orders — read-only market data.\n\n8. Wonderings rotation\n\nKeep a wonderings.md file: a running list of open questions/curiosities.\n\nEach night, pick the oldest uncovered wondering for the deep-dive slot,\nthen mark it covered. When the list is exhausted, the LLM generates\nfresh wonderings from recent discoveries.\n\nNever repeat a covered wondering.\n\n9. Output\n\nTwo files per night in data/dispatches/YYYY-MM-DD.md:\n\nDispatch — markdown with the 4 sections, each with title, link,\n2–3 sentence why-it-matters, and source attribution.\n\nTeaser — a 2-line push notification text (sent via ntfy, see below).\n\nAlso write data/latest.md (symlink or copy of the newest dispatch) and\ndata/teaser.txt for the notification pipeline.\n\n10. Notifications\n\nSend the teaser via ntfy (self-hosted or ntfy.sh) when the dispatch is ready.\n\nIf the run fails entirely, send a failure notice instead of staying silent.\n\nNever send \"ready\" if the dispatch wasn't actually written.\n\n11. State files (all JSON, atomic writes)\n\nFile\n\nPurpose\n\ndata/seen.json\n\nURLs already dispatched (dedupe)\n\ndata/wonderings.md\n\nOpen questions rotation\n\ndata/status.json\n\nLast run status: ok flag, source counts, timestamp\n\ndata/dispatches/\n\nOne markdown file per night, kept indefinitely\n\nWrite state atomically (temp file + rename), never in-place rewrites.\n\n12. Failure discipline (non-negotiable)\n\nNever mark a candidate seen until it is successfully dispatched.\n\nEvery retry loop: attempt counting, a ceiling, a fallback, a notification.\n\nIf all LLM calls fail, still write the dispatch from deterministic picks —\na thinner dispatch beats a missing one.\n\nLog every run; a silent failure is the worst outcome.\n\n13. Dependencies\n\nPython 3 stdlib + requests. That's it.\n\nOne LLM API endpoint (any OpenAI-compatible API works — LiteLLM proxy,\nOpenAI, Groq, local Ollama, etc.).\n\nCron. A lock file. A directory.\n\n14. What it is NOT\n\nNot a news aggregator — no headlines the owner already saw.\n\nNot a trading bot — the markets note is observational only.\n\nNot a chatbot — it produces one artifact per night, no conversation.\n\nStatus as of 2026-10-17. Diagrams are Mermaid and render on GitHub. Facts that drift (agent list, schedules)\nare in CLAUDE.md and manifests/; this document explains how it fits together and why.\n\n1. What Hermes is\n\nAbout 15 small, cron-driven Python agents on one free Oracle Cloud ARM VM, plus a phone-first dashboard, a private\npush-notification server, and two paper forecasting/trading labs. It watches email, school, flyers, fantasy football,\nprediction markets and options, and tells the owner only what matters. Everything is $0: free VM, free-tier model APIs,\nlocal Ollama.\n\n```\nflowchart LR\n  subgraph World[\"Outside world (read-only)\"]\n    MAIL[iCloud mail<br/>IMAP read-only]\n    WEB[School site, flyers,<br/>FPL API, ESPN, FRED]\n    MKT[Kalshi + CBOE + Yahoo<br/>anonymous market data]\n  end\n  subgraph VM[\"One OCI Always Free ARM VM\"]\n    CRON[cron<br/>generated from manifests]\n    AG[~15 agents + 2 labs]\n    LLM[LiteLLM proxy<br/>free lanes + local Ollama]\n    DASH[Dashboard<br/>auth_server + static PWA]\n    NTFY[Self-hosted ntfy]\n    STORE[(SQLite files<br/>per agent)]\n  end\n  OWNER((Owner's phone))\n  MAIL --> AG\n  WEB --> AG\n  MKT --> AG\n  CRON --> AG\n  AG <--> LLM\n  AG --> STORE\n  AG --> NTFY --> OWNER\n  STORE --> DASH\n  DASH -- \"Cloudflare tunnel\" --> OWNER\n```\n\nLoading\n\nWhy one VM: it is free, simple to back up, and a failure has one place to look. Nothing needs more.\n\nThe two paper labs, run as Hermes-framework agents\n\nmemory/, chief/\n\nSearchable memory and the ranked daily \"needs you\" brief\n\nhermes/proposals.py\n\nScans skill and canary proposals; approve opens a proposal/* branch + draft PR\n\ndocs/\n\nThis tour\n\ncd/auto-pull.sh\n\nDeploy: fast-forward only, CI-green commits only\n\n3. How a job runs, and how code gets deployed\n\n``` php\nflowchart TD\n  M[\"agent.toml / manifests/*.toml<br/>schedule in PT\"] --> G[tools/gen_crontab.py<br/>explicit UTC, DST-aware]\n  G --> S[tools/sync_crontab.py<br/>managed block only]\n  S --> C[cron line]\n  C --> R[\"runner/hermes_run.py<br/>flock + manifest timeout\"]\n  R --> A[agent script]\n  R --> DB[(runner/data/runs.db<br/>one row per run)]\n  A --> OUT[agent data/ + card.hermes.json]\n  OUT --> B[dashboard/build_data.py]\n  B --> J[public/data/*.json]\n  J --> PWA[static PWA]\n  DB --> CAN[canary liveness checks]\n```\n\nLoading\n\n``` php\nflowchart LR\n  DEV[Edit + test locally] --> PUSH[git push master]\n  PUSH --> CI[\"GitHub CI<br/>compile, unittest, bash -n,<br/>gitleaks, cron goldens,<br/>paper-only / IMAP / canary greps\"]\n  CI -- green --> PULL[\"VM cron every 15 min<br/>cd/auto-pull.sh (ff-only)\"]\n  PULL --> SYNC[sync crontab] --> RESTART[restart hermes-dash if dashboard/ changed]\n  CI -- red --> HOLD[hold + alert once]\n```\n\nLoading\n\nDecisions and why\n\nSchedules live in manifests, crontab is generated. Ubuntu cron ignores CRON_TZ, and a hand-edited crontab drifts. So\nPT is authored, UTC is generated, and golden files for both PDT and PST are checked in CI, so a DST change needs no human.\n\nPull-based deploy, CI-green only, fast-forward only. The VM never accepts a red or conflicting commit and never needs\ninbound access. The cost is up to ~15 minutes of deploy latency, which nothing here cares about.\n\nstdlib-only agents. No dependency drift on a VM nobody wants to babysit.\n\n4. Models: lanes, not providers\n\n``` php\nflowchart LR\n  AGENT[Agent] -- \"task label + lane name\" --> PROXY[LiteLLM proxy<br/>stateless]\n  PROXY --> D[\"agent-default<br/>Groq 120b > Groq 20b > NVIDIA > Pollinations > Ollama\"]\n  PROXY --> RS[\"agent-reason<br/>Groq 120b > Groq 20b > NVIDIA\"]\n  PROXY --> V[\"agent-vision<br/>Gemini Flash only\"]\n  PROXY --> CH[\"agent-cheap<br/>local Ollama only\"]\n  PROXY --> USAGE[(usage.db<br/>every call logged)]\n  MAILAG[email-brief / home-chief] -- \"local only\" --> OLL[Ollama on the VM]\n```\n\nLoading\n\nAgents name a lane, never a model. Providers can be swapped or reordered in one file and failover is automatic\n(3 strikes then 5-minute cooldown). Why: free tiers rate-limit and disappear; the stack must survive that.\n\nVision never fails over to text models, so an image can never be silently mis-read.\n\nFamily email never leaves the VM. Email agents use local Ollama by policy and a CI grep enforces read-only IMAP.\n\nTask labels are mandatory so usage.db shows which agent burns which quota.\n\n5. Notifications and the dashboard\n\nOne sender:hermes.notify.send(title, msg, channel=\"alerts\"|\"family\") to the self-hosted ntfy server. A test\nforbids posting to public ntfy.sh (only the heartbeat ping is exempt). Why: topics are secrets; one choke point makes redaction\nand rate limits enforceable.\n\nDashboard:auth_server.py on 127.0.0.1:8899 behind a Cloudflare tunnel, ntfy-code login, two roles. The family role can\nreach only /family/ and the flyer upload. Pages: Control Room /, /classic/, /family/, /chat/, /fpl/, /options/ (with a Lab learning panel: lessons and memory index), /proposals/ (the approval inbox, full role only).\n\nContract-first data: agents emit versioned JSON (hermes.card/1, hermes.fleet/1, hermes.equity/1, ...), validated by\nhermes/cards.py. Why: the UI can change without touching agents, and a bad card fails validation instead of breaking the page.\n\n6. The forecasting labs (the interesting part)\n\nThe Hermes framework underneath\n\nThe labs run on Hermes Agent by Nous Research (open source; the VM tracks\nupstream, v0.21.5). It supplies what we would otherwise hand-build: skills (SKILL.md playbooks), an agent loop with memory,\nMCP tool support and a job scheduler. Our cron stays the clock; the framework does the thinking.\n\nEach is a skill-driven agent job with a matching -fallback job (--no-agent deterministic script)\n\noptions-lab-debate-{bull,bear,synth} + -finalize\n\noptions-lab\n\nBlind bull and bear sessions, a synthesis, then a deterministic MC-checked finalize\n\nMCP server options-lab\n\noptions-lab\n\nOur stdlib tool server (market, risk, fills, ledger) registered with hermes mcp add\n\nDashboard chat bridge\n\ndashboard\n\n/api/hermes/chat hands prompts to the framework\n\nrecall tool (inside the options-lab MCP server)\n\noptions-lab\n\nPast similar decisions with outcomes, plus current lessons; lab sources only; used by the decide skill\n\nEverything else (fpl, email-brief, home-chief, school, flyer-events, after-dark, lane-eval, ledger, canary,\nhygiene, vm-backup, memory, chief) is plain cron plus our Python, not framework-hosted. The framework's own messaging\ngateway service is not running; notifications go through our ntfy path instead.\n\nHow much of the framework we use, and why not more. In use: scheduler (as a trigger), skills, MCP, the LiteLLM provider, and\nnow memory-backed recall. Deliberately off: delegation (more calls than an ~8k tokens/min free tier can pay for), web/browser/\nterminal/file/code toolsets (no powerful tools next to the ledger), and the messaging gateway (ntfy does the job). Every tool\nschema costs tokens on every call, so the lab agents run with 10 tools, and the free-tier token budget, not missing features, is\nthe real limit.\n\nBoth labs are paper only. There is no order code, no broker login; a CI grep and a canary invariant enforce it.\n\n``` php\nflowchart TD\n  CRONT[\"cron = the clock only\"] --> FW[fw.py wrapper]\n  FW --> AGJ[\"Hermes framework AGENT job<br/>skill + memory + MCP tools\"]\n  FW -. \"no ledger evidence / lane down\" .-> FB[\"deterministic --no-agent<br/>fallback job\"]\n  AGJ --> MCP[\"stdlib MCP server<br/>market, risk, fills, ledger\"]\n  FB --> MCP\n  MCP --> L[(\"append-only ledger<br/>integer cents / kalshi.db\")]\n  L --> SC[Brier scoring vs market baseline]\n  SC --> REV[weekly rule-based review<br/>tunes policy inside fixed envelopes]\n  REV --> MCP\n  L --> INV[\"loop-closure invariant<br/>re-checked independently by canary\"]\n```\n\nLoading\n\nLab\n\nIdea\n\nSuccess test\n\nkalshi-hermes\n\nLLM estimates event probabilities from a numbered evidence packet (source + as-of time), blind to the market price\n\nBeats the market's own price by Brier score, n >= 30 with a bootstrap CI\n\noptions-lab\n\n$5k x 6 sleeves = $30k virtual; scout, debate (blind bull and bear), decide, manage, learn\n\nStaged rollout (baseline alone first); phase gates before it may be called \"live\"\n\nDecisions and why\n\nAgents propose, deterministic code disposes. Models never touch money logic: sizing, risk limits, fills and ledger writes are\nplain code behind MCP tools. A confused model can waste tokens but cannot corrupt the books.\n\nAlways a fallback job. If the agent leaves no ledger evidence, a deterministic run still happens, so the clock never stalls.\n\nThe ledger is append-only with a loop-closure invariant (every decision ends in a recorded outcome). Calibration is only trustworthy if nothing is rewritten.\n\n\"Beat the baseline or it doesn't count.\" Every forecast stores model P, market P and later resolution at decision time.\nFair tests only: markets whose outcome is already effectively known (games underway, prices near 0 or 1) are skipped.\n\nNo self-congratulation. Docs may not say \"live\", \"working\" or \"learning\" until the phase gates are met.\n\nPolicy tuning is bounded: weekly, rule-based, inside fixed envelopes, versioned.\n\n7. Safety rails (why it can run unattended)\n\n```\nflowchart LR\n  subgraph Prevent[\"Prevent (CI)\"]\n    P1[gitleaks secret scan]\n    P2[grep: no order code]\n    P3[grep: IMAP no STORE/DELETE/MOVE]\n    P4[manifest + cron goldens PDT/PST]\n    P5[test: no public ntfy.sh]\n  end\n  subgraph Detect[\"Detect (canary)\"]\n    D1[run liveness vs manifest]\n    D2[loop-closure + cash reconciliation]\n    D3[paper-only / IMAP re-check]\n  end\n  subgraph Respond[\"Respond\"]\n    R1[ntfy alert]\n    R2[\"pause agent only for<br/>paper-only, IMAP, outbound-mail rules\"]\n    R3[propose fix in a throwaway worktree<br/>never auto-apply]\n  end\n  Prevent --> Detect --> Respond\n```\n\nLoading\n\nThe canary is propose-only. Its fixes land on a canary/* branch built in a throwaway git worktree, never the live\ncheckout, and it cannot edit its own policy files. It can pause an agent only for a short list of safety rules. Why: an\nautonomous fixer with write access to production is a bigger risk than the bugs it fixes.\n\nBackups: nightly encrypted vm-backup, pushed to OCI Object Storage via instance principal, create-only (it cannot delete\nits own backups). RESTORE.md is the runbook; the options lab also does a weekly restore drill.\n\nSecrets (.env, ntfy topics, tokens, Kalshi key, FPL team id) are never committed, printed or logged; crontabs are redacted when shown.\n\nThe VM checkout is never reset or cleaned. It has intentional drift; ff-only pulls tolerate that.\n\n8. Memory, Chief of Staff and the PR-writing canary\n\n```\nflowchart LR\n  SRC[\"Cards, briefs, ledger rationales,<br/>run logs, derived email briefs\"] --> IDX[\"memory/index.py<br/>nightly 2:30am PT\"]\n  IDX --> RED[redact secrets, emails, phones]\n  RED --> MEM[(\"memory.db<br/>SQLite FTS5 + optional embeddings\")]\n  MEM --> API[\"hermes.memory<br/>add / seen / mark_pushed / search\"]\n  API --> CHIEF[\"chief/ ranks 'needs you today'<br/>7:00am + 6:00pm PT\"]\n  CARDS[fleet + agent cards] --> CHIEF\n  CHIEF -- \"max 3/run, 6/day, quiet hours\" --> NT[ntfy push + Control Room card]\n  API --> CHAT[\"chat retrieval (planned)<br/>private sources excluded\"]\n  INC[Agent failure] --> CAN[\"canary propose_fix<br/>CANARY_PR_MODE off / local / draft\"]\n  CAN --> BUN[\"patch + regression test + PR.md<br/>reviewed by a human\"]\n```\n\nLoading\n\nPiece\n\nValue\n\nKey guardrail\n\nMemory\n\nOne searchable place for everything agents saw or produced; dedupes alerts, gives agents their own history, base for chat\n\nRedaction before storage; private sources excluded from cloud chat; local-only embeddings\n\nChief of Staff\n\nTurns ~15 agents' output into 1 to 3 pushes that matter; the antidote to notification fatigue\n\nHard caps, quiet hours, private text summarised only by local Ollama\n\nPR-writing canary\n\nFailures arrive as a ready-to-review fix plus regression test instead of a bare alert\n\nDefault off; never merges; draft mode only pushes a draft PR when the owner enables it\n\nLearning loop and the Proposals inbox\n\n``` php\nflowchart TD\n  LEDGER[(\"options ledger<br/>closed trades + outcomes\")] --> IDXL[\"memory index_lab<br/>lab_outcome / lab_debate / lab_review\"]\n  IDXL --> MEM[(memory.db)]\n  MEM --> RECALL[\"recall tool (kind=recall)<br/>5 compact hits\"]\n  RECALL --> DECIDE[decide agent]\n  LEDGER --> REVIEW[\"weekly rule-based review<br/>(no LLM)\"]\n  REVIEW --> LES[\"lessons.md / lessons.json<br/>max 10 bullets, 1500 chars, 8-week expiry\"]\n  LES --> RECALL2[\"recall tool (kind=lessons)\"] --> DECIDE\n  LES --> PROP[\"tools/lab_skill_proposals.py<br/>one LLM call, proposes a SKILL.md edit\"]\n  CAN[canary propose_fix<br/>CANARY_PR_MODE=local bundle] --> INBOX\n  PROP --> INBOX[\"Dashboard Proposals inbox<br/>summary, evidence, diff, tests badge\"]\n  INBOX -- \"Dismiss\" --> DONE[status only]\n  INBOX -- \"Approve (full role only)\" --> BR[\"throwaway worktree off master<br/>push proposal/&lt;id&gt; + draft PR\"]\n  BR --> YOU[[\"You review and merge on GitHub\"]]\n  YOU --> CI[CI] --> DEPLOY[VM auto-pull + install.py refreshes skills]\n```\n\nLoading\n\nLessons are statistics, not opinions. They come from ledger numbers with minimum evidence, so noise is not promoted; they expire so\nthe agents do not anchor on stale conclusions.\n\nNothing self-modifies. A proposal is a file until a human merges a PR. The approve button can only create a proposal/* branch from\nmaster (never force, never master), restricted to the proposal's own files; the framework installer ignores the proposals folder.\n\nProposals and approvals are visible only to the full role. Skill proposals are generated outside options-lab/ because the canary\nforbids model calls inside lab code.", "url": "https://wpnews.pro/news/hermes-architecture-a-10-minute-tour-as-of-2026-10-17", "canonical_source": "https://gist.github.com/abhinavag-svg/4d698ef9e2d5530dd809f687c4c797e9", "published_at": "2026-10-19 22:06:15+00:00", "updated_at": "2026-10-20 05:14:29.948605+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "large-language-models", "developer-tools"], "entities": ["Hermes", "Hacker News", "arXiv", "GitHub", "Kalshi", "ntfy", "SEC EDGAR"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/hermes-architecture-a-10-minute-tour-as-of-2026-10-17", "markdown": "https://wpnews.pro/news/hermes-architecture-a-10-minute-tour-as-of-2026-10-17.md", "text": "https://wpnews.pro/news/hermes-architecture-a-10-minute-tour-as-of-2026-10-17.txt", "jsonld": "https://wpnews.pro/news/hermes-architecture-a-10-minute-tour-as-of-2026-10-17.jsonld"}}