The repo rendered as answers to the canonical register — Core (C#), Memory bank (M#), plus two DRAFT questions it answers unusually well.
C1
Source of truth
Chose: plain files the user owns under ~/.hermes — config.yaml for behavior, .env for secrets only, Markdown memory, skill folders — plus one SQLite state.db for every transcript. Profiles are separate HERMES_HOME dirs.
Why: profiles are independent islands on purpose
— a PR coupling them was closed for exactly that.
Trade-off: no hosted sync; portability is a migration command, not a service.
hermes_constants.py · hermes_state.py · AGENTS.md
C2
Trust & safety boundaries
Chose: radical honesty. The only security boundary against an adversarial LLM is the operating system.
Every in-process screen is officially a heuristic. Real rails: OS/container isolation, default-deny allowlists at every network surface, salted-hash DM pairing.
Why: a denylist over a Turing-complete shell is structurally incomplete
— so don't pretend otherwise.
Trade-off: the default local backend runs on the host; untrusted input there is declared outside the supported posture.
SECURITY.md · gateway/authz_mixin.py · gateway/pairing.py → step 4
C3
Cost model
Chose: no single spend cap — six layered caps, each for a different runaway shape: iteration budget (500/turn), tool-result character budgets, compression at 50% of window, opt-in loop circuit-breakers, a 429-amplification guard, provider-side credit caps. Cap #0 is the constitution itself: never invalidate the prompt cache.
Why: cache reuse multiplies the user's cost
when broken.
Trade-off: nothing locally caps dollars per day.
agent/iteration_budget.py · tools/budget_config.py · agent/nous_rate_guard.py
C4
Integration surface
Chose: many bodies, one agent. One AIAgent class serves CLI, gateway, ACP, batch, and API server. Platform differences live in the entry point, not the agent.
The dashboard doesn't rebuild chat in React — it embeds the real terminal UI over a PTY.
Why: anything added to the TUI shows up in the dashboard automatically.
Trade-off: the shared core grew into god-files; the PTY trick is POSIX-only.
run_agent.py · mcp_serve.py · acp_adapter/ · hermes_cli/pty_bridge.py
C5
Extension model
Chose: the Footprint Ladder — extend existing → CLI command + skill → gated tool → plugin → curated MCP catalog → new core tool last. Third-party product plugins and new memory providers are closed out of the tree; they ship as standalone repos.
Why: We are expansive at the edges and conservative at the waist
; in-tree vendor code is our burden to keep working against a fast-moving core.
Trade-off: capability arrives less ergonomically (a skill + shell, not a typed tool), and out-of-tree plugins lose discoverability.
AGENTS.md · toolsets.py · optional-mcps/ → step 9
C6 · draft
Honesty discipline
Chose: a prompt-text rail shipped to every session: the deliverable is a working artifact backed by real tool output — not a description of one… NEVER substitute plausible-looking fabricated output.
Extra scaffolding for GPT/Grok models that declare done without verifying. Contributor side: if you can't point to the exact line where the bug manifests… you haven't verified the premise.
Trade-off: it's all prose — no machine-checked evidence gate. The one structural exception: computer-use returns a driver-verified effect verdict the model may not override.
agent/prompt_builder.py:344 · AGENTS.md
F9 · draft
Untrusted-content boundary
Chose: framing over filtering. Web/browser/MCP results get wrapped in untrusted-data delimiters (embedded fake delimiters neutralized); hostile chat names are JSON-quoted at the gateway. Hard blocking is reserved for surfaces the user can't intervene on: context files, memory writes, cron prompts, skill installs. Strongest move: capability removal — the webhook toolset is just 4 read-only tools.
Why: it changes how the model interprets the content rather than relying on regex… catching every payload.
Trade-off: prompt injection per se is declared out of bug-bounty scope; only chained outcomes count.
agent/tool_dispatch_helpers.py:544 · tools/threat_patterns.py · toolsets.py:90 → steps 1, 4
M1
Storage & index
Chose: the core brain is two Markdown files (agent notes + user profile) with hard character caps, plus one SQLite file holding every session, indexed by three trigger-maintained FTS5 tables. Eight pluggable external providers (Honcho, mem0, …), max one active.
Why: character caps because char counts are model-independent
; local-first, no service dependency.
Trade-off: ~1,300 tokens of curated memory total — tiny by design.
tools/memory_tool.py · hermes_state_common.py · plugins/memory/
M2
Unit of memory
Chose: a free-text entry split by a § delimiter. No IDs, no timestamps, no embeddings — edits address entries by substring.
Why: the file is small enough that the agent is the index.
Trade-off: no dedup machinery; curation is the model's job, forced by the cap (see M8).
tools/memory_tool.py
M3
Schema & typing
Chose: schemaless memory; typed everything else. Skills carry agentskills.io frontmatter (name ≤64, description effectively ≤60 chars); sessions live in a migrated SQLite schema with provenance ordering (derived < llm < user) so a model can never overwrite a title you typed.
Trade-off: memory can't be queried structurally — that's what session search and providers are for.
tools/skills_tool.py · hermes_state_schema.py
M4
Ingestion & capture
Chose: agent-curated writes during the turn, plus a post-turn background review fork. The famous periodic nudges
are not injected text — they're counters (10 user turns without a memory write; 10 tool iterations without a skill write) that trigger the fork. A filing gate strict-scans every entry because it enters the system prompt.
Why: a poisoned entry persists for the entire session and across sessions.
Trade-off: human review of writes is opt-in, not default.
agent/turn_finalizer.py · agent/background_review.py → step 6
M5
Linking & structure
Chose: deliberately flat. No typed graph, no backlinks — memory entries are prose; skills structure knowledge as folders (SKILL.md + references/). Relationship modeling is outsourced to Honcho's peer cards when you want it.
Trade-off: "who works where"-style hops need an external provider; the built-in brain can't follow arrows.
plugins/memory/honcho/
M6
Retrieval stack
Chose: keyword-only, zero LLM. FTS5 + BM25 with hand-tuned rank surgery: kanban/subagent sessions hidden, cron sessions demoted (their repetitive vocabulary caused "recall blindness", starving out real conversations), compaction payloads stripped. Model-invoked only — never fires automatically.
Why: No LLM calls anywhere — every shape returns actual messages from the DB.
~20ms, free.
Trade-off: no semantic recall in the core; the README's "LLM summarization" claim is stale (removed in PR #27590).
tools/session_search_tool.py · hermes_state_search.py → step 7
M7
Compile vs retrieve
Chose: both sides of the fork, split by kind: compiled understanding goes into skills (how-to) and the memory snapshot (who-you-are); retrieval stays raw — full transcripts searchable forever. The bridge: compacted turns leave the model's window but stay flagged discoverable, so compression never shrinks the brain.
Why: memory captures who the user is
; skills capture how to do this class of task for this user.
hermes_state.py:7656 · agent/background_review.py
M8
Consolidation & maintenance
Chose: consolidation at the moment of overflow: a full memory file returns an error that instructs the agent to merge and retry in the same turn — no auto-compaction ever. Skills get the curator: inactivity-triggered (weekly, after 2h idle), deterministic stale→archive transitions; its LLM consolidation pass is opt-in and off.
Why: silent dropping is worse than a visible squeeze.
Trade-off: a turn occasionally spends effort housekeeping instead of answering.
tools/memory_tool.py · agent/curator.py → step 7
M9
Forgetting & pruning
Chose: almost nothing deletes. The curator's maximum destructive action is archive (restorable, agent-created skills only, pinned exempt). Compaction soft-archives rows (active=0) that stay searchable. Real DELETE exists only behind user-typed commands with dry-runs.
Why: Zero uses is absence of evidence, not proof the skill is disposable.
Trade-off: the store only grows; shrinking is a chore the user owns.
agent/curator.py · hermes_cli/sessions_cmd.py