Explanation¶
Hermes Agent is a Python agent built around one synchronous orchestration class, AIAgent, that every entry point shares: CLI/TUI, messaging gateway, desktop backend, API server, ACP, cron, and batch runs. Pluggable memory, skills, terminal backends, platform adapters, and provider profiles sit around that core. This page explains how the subsystems work and why they are designed that way, including the self-improvement loop, the threat model, and the isolation design. Exact defaults and limits are in Reference.
Version context
Written against v0.21.5 (2026-09-24). The codebase changes weekly; v0.15.0 (2026-05-28) split the old monolithic run_agent.py into agent/ modules, so older blog posts describe a different file layout.
System Architecture¶
The component map below follows the upstream Architecture page: several entry points build an AIAgent, which resolves a provider, assembles the prompt, dispatches tools, and persists to one SQLite store per profile.
flowchart TB
subgraph Entry["Entry points"]
CLI["cli.py<br/>HermesCLI / TUI"]
GW["gateway/run.py<br/>GatewayRunner"]
API["API server<br/>:8642 OpenAI-compatible"]
ACP["acp_adapter<br/>VS Code / Zed / JetBrains"]
CRON["cron/scheduler.py"]
SERVE["hermes serve<br/>Desktop backend"]
end
subgraph Agent["AIAgent (agent/conversation_loop.py)"]
PB["prompt_builder.py<br/>stable / context / volatile tiers"]
RP["runtime_provider.py<br/>3 API modes"]
MT["model_tools.py + tools/registry.py<br/>70+ tools, ~28 toolsets"]
CC["context_compressor.py<br/>(ContextEngine plugin slot)"]
MM["memory_manager.py<br/>(MemoryProvider plugin slot)"]
end
subgraph State["Profile state (HERMES_HOME)"]
DB[("state.db<br/>SQLite WAL + FTS5")]
MEM["memories/MEMORY.md<br/>memories/USER.md"]
SK["skills/*/SKILL.md"]
end
subgraph Backends["Tool backends"]
TERM["Terminal: local, docker, ssh,<br/>modal, daytona, vercel_sandbox, singularity"]
BR["Browser, web, vision"]
MCP["MCP servers"]
end
subgraph Learn["Learning loop (background)"]
REV["Background review fork"]
CUR["Curator"]
end
Entry --> Agent
RP --> LLM["LLM provider<br/>(Nous Portal, OpenRouter, Anthropic, ...)"]
MT --> Backends
Agent --> DB
MM <--> MEM
PB --> SK
Agent -.->|after turn| REV
REV -.->|memory tool / skill_manage| MEM
REV -.-> SK
CUR -.->|stale / archive / consolidate| SK
Agent Loop¶
The core of Hermes Agent is AIAgent.run_conversation() (with chat() as a thin wrapper). run_agent.py is now a facade; the loop lives in agent/conversation_loop.py and each turn phase in agent/turn_*.py. It handles provider selection, prompt construction, tool execution, retries, fallback, context compression, and session persistence.
Turn Lifecycle¶
Each iteration follows the sequence documented in Agent Loop Internals:
- Generate task ID and append the user message
- Build or reuse the cached system prompt (
prompt_builder.py) - Preflight compression check: compress when usage passes about 50% of the context window
- Build API messages for the active API mode (OpenAI format as-is, Responses input items, or Anthropic via
anthropic_adapter.py) - Inject ephemeral prompt layers (budget warnings, context pressure) at call time, not into the cached prefix
- Apply prompt-caching markers when talking to Anthropic
- Interruptible API call (
_interruptible_api_call) on a background thread, abandoned if the user interrupts - If tool calls: dispatch via
handle_function_call()(parallel calls run in aThreadPoolExecutor, interactive tools such asclarifyforce sequential order), append results, loop back to step 4 - If text: persist the session, flush memory if needed, return
flowchart TD
A[User Message] --> B[Build System Prompt]
B --> C{"Context above ~50%?"}
C -->|Yes| D[Compress Context]
C -->|No| E[Build API Messages]
D --> E
E --> F[Inject Ephemeral Layers]
F --> G["Interruptible LLM API Call"]
G --> H{Response Type}
H -->|Tool Calls| I["Dispatch via handle_function_call"]
I --> J[Append Results]
J --> E
H -->|Text| K["Persist Session to state.db"]
K --> L[Flush Memory]
L --> M[Return Response]
Prompt Architecture¶
The prompt is assembled in ordered tiers, stable then context then volatile: identity (SOUL.md), tool guidance and the skills index first; then project context files; then memory, user profile, and timestamp blocks. The design principle is prompt stability: the system prompt does not change mid-conversation, because any change invalidates provider-side prefix caching and re-bills the full input.
That principle explains several user-visible behaviours:
- Memory is injected as a frozen snapshot at session start. A fact saved mid-session appears in the prompt only in the next session.
- Ephemeral items (budget warnings, context pressure) are added at call time, never baked into the prefix.
/modelswitches and/reload-mcpare treated as explicit, cache-breaking user actions (/reload-mcpasks for confirmation by default).
Only one project context type loads per session (first match wins): .hermes.md -> AGENTS.override.md -> AGENTS.md -> CLAUDE.md -> .cursorrules. SOUL.md is always loaded from HERMES_HOME.
The system supports three API modes: chat_completions (OpenAI-compatible, the default), codex_responses (OpenAI Responses), and anthropic_messages (native Anthropic). All three convert to and from one internal OpenAI-style message format.
Memory System¶
Hermes combines always-in-context memory with on-demand recall. The docs frame it as a trade-off: a small, curated store costs tokens on every turn, so it holds only durable facts, while unlimited history stays in SQLite and is searched only when needed.
flowchart TB
subgraph L1["Layer 1: Session Context"]
SC["In-memory messages<br/>compressed near the limit"]
end
subgraph L2["Layer 2: Persistent Memory"]
PM["MEMORY.md 2,200 chars<br/>USER.md 1,375 chars"]
end
subgraph L3["Layer 3: Session History"]
SH["state.db SQLite + FTS5<br/>session_search tool"]
end
subgraph L4["Layer 4: Skills"]
SK["SKILL.md procedures<br/>progressive disclosure"]
end
subgraph EXT["Optional: External Provider"]
EP["Honcho, Mem0, OpenViking, ...<br/>one active at a time"]
end
SC -->|"memory tool writes"| PM
SC -->|"every turn persisted"| SH
SC -->|"skill_manage writes"| SK
PM -->|"frozen snapshot at session start"| SC
SH -->|"search on demand"| SC
SK -->|"skill_view on demand"| SC
PM -.->|mirrored| EP
EP -.->|"prefetch + tools"| SC
Layer 1 -- Session Context¶
The in-memory message list for the current conversation. It is compressed automatically (lossy summarization of middle turns by context_compressor.py, or a plugin context engine) as it approaches the provider's limit. On messaging platforms a chat is one continuous session that survives restarts, which is why the docs recommend /new at natural boundaries: the learning loop of forget -> recall from memory -> search past sessions only fires across session boundaries.
Layer 2 -- Persistent Memory (MEMORY.md and USER.md)¶
Two bounded files in ~/.hermes/memories/: MEMORY.md for the agent's notes (environment facts, conventions, lessons) and USER.md for the user profile (preferences, style). The agent edits them only through the memory tool (add, replace, remove with substring matching). There is no read action because the content is already in the system prompt.
Design choices worth knowing:
- No auto-compaction. When a write would exceed the character limit, the tool returns an error with the current entries, and the agent must consolidate in the same turn.
- Periodic nudges. Every
memory.nudge_intervaluser turns (default 10) the agent is reminded to consider saving memory. - Injection scanning. Entries are scanned for prompt-injection, credential-exfiltration, and invisible-Unicode patterns before acceptance, because they land in the system prompt.
- Per-profile scope. Two agent processes must not share one Hermes home; use profiles or an external provider for shared memory.
Correction from earlier versions of this page
Earlier notes described a three-mode Honcho layer (local / honcho / hybrid) as the default memory. Current docs make the built-in MEMORY.md/USER.md files the default. Honcho is one optional external provider, and its recallMode values are hybrid, context, and tools.
Layer 3 -- Session History (SQLite + FTS5)¶
All CLI and messaging sessions go to ~/.hermes/state.db (SQLite in WAL mode for concurrent readers plus one writer). Messages are indexed by FTS5 tables, including a trigram index for CJK and substring search. Sessions keep lineage across compressions (parent/child) and per-platform isolation.
The agent recalls history through the session_search tool. Per the current docs it makes no LLM calls and returns views of actual messages that the agent can scroll through (~20 ms per query). The project README still says "FTS5 session search with LLM summarization"; the dedicated memory and sessions pages are the more specific and more recent source.
Layer 4 -- Skills¶
Procedural memory stored as SKILL.md directories. See Skill Engine.
External Memory Providers¶
A MemoryProvider plugin adds a second store alongside the built-in files. It is exclusive: only one is active at a time. When active, Hermes injects provider context, prefetches relevant memories before each turn, syncs turns after each response, extracts memories at session end (if supported), mirrors built-in memory writes, and adds provider tools.
Honcho (by Plastic Labs) is the best-known option. It adds dialectic user modeling. In hybrid or context mode it injects two layers each turn:
- Base context: session summary, user representation, user peer card, AI self-representation, AI identity card
- Dialectic supplement: LLM-synthesized reasoning about the user's current state (
peer.chat(), up to 3 passes, everydialecticCadenceturns)
Both are truncated to contextTokens if set. The full provider list is in Reference: External Memory Providers.
Skill Engine¶
The skill engine is Hermes Agent's core differentiator. It lets the agent create, store, retrieve, and improve reusable task knowledge without a human writing it. Skills follow the agentskills.io open standard, so they are portable to other agent CLIs that read .agents/skills/.
Skill Lifecycle¶
The state diagram shows how an agent-created skill moves between states. Creation and patches come from the agent; stale and archived come from the curator.
stateDiagram-v2
[*] --> Staged: skill_manage create (write_approval on)
[*] --> Active: skill_manage create (write_approval off)
Staged --> Active: /skills approve
Staged --> [*]: /skills reject
Active --> Active: patch after errors or user correction
Active --> Stale: unused 14 days (curator)
Stale --> Active: used again
Stale --> Archived: unused 30 days (curator)
Active --> Archived: consolidated into umbrella skill (opt-in)
Archived --> Active: restore from skills/.archive
Progressive Disclosure¶
Skills cost almost nothing until used. The agent sees only an index (skills_list: name, description, category, about 3k tokens), loads a full SKILL.md with skill_view(name), and loads individual reference files with skill_view(name, path) only when a question needs them. That is why /learn can turn a whole book into a knowledge-base skill: a lean SKILL.md plus one distilled file per chapter under references/.
Autonomous Creation¶
The system prompt asks the agent to record a non-trivial workflow with skill_manage when it worked out a repeatable multi-step procedure, hit dead ends and found the working path, or was corrected by the user. A nudge reinforces this: every skills.creation_nudge_interval tool-calling iterations (default 15) the model is reminded to consider saving a skill. Early descriptions of Hermes cited a "5+ tool calls" threshold; current docs and config describe this nudge mechanism instead.
The docs set a content standard: skills capture lessons, not logs. A pitfall is a generalizable rule plus one clause of why, attached to the step it affects. PR numbers, dates, and incident narratives are flagged by an advisory linter.
Self-Improvement¶
Skills are patched in place when the agent meets outdated content (an API changed), incomplete coverage (a missed edge case), or incorrect output. The preferred action is a targeted patch (old_string -> new_string) because only the changed text travels in the tool call.
Most of this learning happens in the background review: after a turn, Hermes forks the agent (same system prompt, conversation snapshot, and tools, so it can reuse the warm prompt cache) and lets it decide whether to save memory or create/patch skills. The fork never touches the live conversation. It can run on a cheaper auxiliary model, in which case it replays a compact digest instead of the full transcript.
The sequence below shows one turn followed by the background review and the optional write-approval gate.
sequenceDiagram
participant U as User
participant A as AIAgent
participant P as LLM provider
participant R as Background review fork
participant G as write_approval gate
participant S as MEMORY.md / skills/
U->>A: message
A->>P: prompt with frozen memory snapshot and skills index
P-->>A: tool calls, then final text
A-->>U: response
A->>R: fork with conversation snapshot (post-turn)
R->>P: should anything be remembered or turned into a skill?
P-->>R: memory add or skill_manage patch
R->>G: proposed write
alt write_approval false (default)
G->>S: write immediately, notify "Memory updated"
else write_approval true
G->>G: stage under pending, wait for /memory approve or /skills approve
end
Note over S,A: New memory appears in the next session's snapshot
Curator¶
Without maintenance, autonomous creation produces dozens of narrow near-duplicates. The curator is a background pass, triggered by inactivity (default: at most every 7 days, after 2 idle hours), that tracks view/use/patch counts for agent-created skills. It marks skills unused for 14 days as stale and archives those unused for 30 days. It never deletes: the worst case is a recoverable move into ~/.hermes/skills/.archive/. Pinned skills and skills referenced by cron jobs are skipped. An LLM consolidation pass that merges overlapping skills into umbrella skills is opt-in (curator.consolidate: true) because it costs auxiliary-model tokens and makes broad structural changes. Hub-installed skills are never touched, and bundled skills only with prune_builtins: true.
Skill Discovery¶
Skills load from three tiers, highest precedence first:
- Project skills:
<repo>/.hermes/skills/and<repo>/.agents/skills/. They load only afterhermes skills trust, because a cloned repo could ship malicious procedures. - Local skills:
~/.hermes/skills/, the source of truth. It holds bundled skills (seeded on install and on eachhermes updateunless opted out), hub-installed skills, and agent-created skills. - External directories:
skills.external_dirs, shared with other tools.
Hub installs (hermes skills install) come from official optional skills, skills.sh, well-known endpoints, direct URLs, GitHub taps, ClawHub, LobeHub, and browse.sh, with a security scan at install time.
Self-Evolution System (DSPy + GEPA)¶
The companion repository hermes-agent-self-evolution is a separate, offline optimizer. It is not the in-product learning loop above. It uses DSPy with GEPA (Genetic-Pareto reflective prompt evolution) to produce better versions of Hermes' own artifacts, and it delivers results as pull requests against hermes-agent, never as live edits.
Scope as of 2026-09
Only Phase 1 (evolving SKILL.md files) is implemented. Tool descriptions, system prompt sections, tool code (via Darwinian Evolver), and a continuous loop are listed as planned. Earlier versions of this page implied all four were live.
Evolution Pipeline¶
flowchart TD
A["Read current SKILL.md"] --> B["Generate eval dataset<br/>(synthetic or sessiondb)"]
B --> C[GEPA optimizer]
T["Execution traces"] --> C
C --> D[Candidate variants]
D --> E[Evaluate against dataset]
E --> T
E --> F{"Constraint gates<br/>tests, size limits, benchmarks"}
F -->|Fail| C
F -->|Pass| G[Best variant]
G --> H["PR against hermes-agent<br/>human review"]
GEPA Process¶
- Reflect: GEPA reads execution traces to understand why a candidate failed, not just that it failed, and writes targeted natural-language mutations.
- Evaluate: each variant runs against the eval set (synthetic examples or real history from Hermes, Claude Code, and Copilot session stores).
- Select: GEPA keeps a Pareto frontier of candidates, those that are best on at least some evaluation instances, rather than one global winner, which keeps diverse strategies alive.
- Gate: survivors must pass the full test suite, size limits (skills <= 15 KB, tool descriptions <= 500 chars), caching compatibility, and semantic-preservation checks before a PR is opened.
Operational Characteristics¶
- No GPU training: everything is API calls (mutate text, evaluate, select)
- Cost: about $2-10 per optimization run (per the README)
- Research basis: GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning (arXiv 2507.19457), an ICLR 2026 Oral. The paper reports GEPA beating GRPO by about 6% on average (up to 20%) with up to 35x fewer rollouts, and beating MIPROv2 by over 10%.
- MIT licensed; Darwinian Evolver (AGPL v3) would be used only as an external CLI
Multi-Platform Gateway¶
The messaging gateway is one long-running process (gateway/run.py, GatewayRunner) that connects to every configured platform, authorizes users, maps each chat to a session, dispatches slash commands, runs cron ticks, and does background maintenance (including curator checks). Since the multi-profile work, one host gateway can multiplex several profiles (hermes gateway migrate).
Platform Adapters¶
Adapters are either built into gateway/platforms/ (for example Signal, Weixin, BlueBubbles, QQ, WhatsApp Cloud, Yuanbao, webhook, API server) or shipped as bundled platform plugins under plugins/platforms/ (Telegram, Discord, Slack, WhatsApp, Matrix, Mattermost, Email, SMS, DingTalk, Feishu, WeCom, Home Assistant, IRC, LINE, Teams, Google Chat, Buzz, ntfy, Photon, Raft, SimpleX). Third parties can add platforms with ctx.register_platform(). The full list with capability flags is in Reference: Messaging Platforms.
All platforms get full tool access, not just chat: the same agent capabilities are available from Telegram as from the CLI. This is the main design difference from chat-bot frameworks, and also the main source of risk (see Threat Model).
Gateway Architecture¶
The flow for one inbound message, per Gateway Internals:
sequenceDiagram
participant PL as Platform (Telegram, Slack, ...)
participant AD as Adapter.on_message
participant GR as GatewayRunner
participant AU as Authorization
participant AG as AIAgent
participant DB as state.db
PL->>AD: platform event
AD->>GR: MessageEvent
GR->>AU: allow-all flags, pairing list, allowlists
AU-->>GR: allowed or denied (default deny)
GR->>DB: resolve session key, load history
GR->>AG: run_conversation with history
AG-->>GR: final response (tool progress streamed)
GR->>AD: deliver response, media, typing indicators
AD->>PL: send
Terminal Backends¶
Seven backends decide where the agent's shell commands, file tools, and execute_code run: local, docker, ssh, modal, daytona, vercel_sandbox, singularity. The design separates where Hermes runs from where the agent's commands run. You can run Hermes on a $5 VPS or laptop and point execution at a Modal or Daytona sandbox that hibernates when idle, or at a GPU box over SSH.
Two design decisions stand out:
- The Docker backend is one long-lived container by default, shared across sessions,
/new, subagents, and even Hermes restarts (found again by label). This keeps installed packages and background processes (dev servers, watchers) alive, at the cost of cross-session isolation.container_persistent: falseswitches to one fresh container per session when the sandbox must separate conversations. - Cloud persistence means filesystem state, not live processes. Modal snapshots and restores the filesystem; Daytona stops and resumes; Vercel Sandbox uses snapshot-backed persistence. PIDs and background jobs do not survive.
The per-backend comparison table is in Reference: Terminal Backends.
Plugin System¶
Plugins are discovered from five sources: bundled (<repo>/plugins/), user (~/.hermes/plugins/), project (.hermes/plugins/, only with HERMES_ENABLE_PROJECT_PLUGINS=true), pip entry points (hermes_agent.plugins), and Nix declarative config. A plugin's register(ctx) function can register:
- Tools: schemas and handlers
- Hooks: 27 lifecycle events, for example
pre_tool_call,post_tool_call,pre_llm_call,on_session_start,subagent_stop - Slash commands and CLI subcommands
- Platforms, image-generation backends, and model-provider profiles
Four plugin kinds exist. General plugins are multi-select. Memory providers and context engines are single-select (one active). Model providers can all be registered at once and are picked with --provider. General plugins are disabled by default: discovery lists them, but none of their code runs until the name is added to plugins.enabled. This design came from treating plugins as untrusted third-party code.
Security Track Record¶
Correction: Hermes Agent does have CVEs
Earlier versions of this page (and the OpenClaw topic) claimed "zero agent-specific CVEs as of April 2026". That is no longer true. At least ten CVEs were published against hermes-agent between April and September 2026. They cover API-server authentication (CVE-2026-7112), approval-guard authorization (CVE-2026-9350), cross-user session access (CVE-2026-11461), webhook path traversal (CVE-2026-14628), SSRF (CVE-2026-85106), and memory-scanner injection (CVE-2026-10223). See Reference: Known Security Advisories.
The advisories cluster where the threat model below predicts: the network-facing gateway and API server, the approval layer, and session isolation. The project has responded with structural hardening rather than one-off fixes. Core dependencies are exact-pinned by policy since 2026-05-12, after a PyPI supply-chain worm (a few web-server packages such as fastapi and urllib3 still use bounded ranges). hermes security audit checks installed packages against OSV. A non-loopback dashboard now fails closed without auth. API_SERVER_KEY is required even on loopback. v0.21.0 added write approval for protected instruction files and a redaction sweep. The repository's SECURITY.md sets a 90-day coordinated-disclosure window. Several of the CVEs were filed through VulDB-style public disclosure (for example, the GitHub advisory for CVE-2026-7112 says the project had not responded when it was published), so the CVE record alone often does not say which release fixed the issue.
Threat Model¶
The attack surface splits into what the network can reach and what runs with the agent's privileges.
flowchart TB
subgraph External["External Attack Surface"]
GW["Gateway Channels<br/>(Telegram, Discord, Slack, ...)"]
API["API Server<br/>(:8642 REST)"]
WEB["Web Dashboard<br/>(:9119)"]
WH["Webhooks<br/>(inbound events)"]
end
subgraph Internal["Internal Attack Surface"]
PLUGIN["Plugin System<br/>(arbitrary Python)"]
SKILL["Skills and memory<br/>(prompt-level instructions)"]
TERM["Terminal Backends<br/>(code execution)"]
KEYS["Credentials<br/>(.env, auth.json)"]
DB["Session Database<br/>(state.db)"]
MCPS["MCP servers<br/>(subprocesses)"]
end
GW -->|"allowlists, DM pairing, default deny"| AGENT[AIAgent]
API -->|"API_SERVER_KEY required"| AGENT
WEB -->|"loopback, or auth provider required"| AGENT
WH -->|"per-route HMAC secret"| AGENT
AGENT --> PLUGIN
AGENT --> SKILL
AGENT --> TERM
AGENT --> KEYS
AGENT --> DB
AGENT --> MCPS
Attack Surfaces¶
| Surface | Risk | Mitigation |
|---|---|---|
| Gateway channels | Messages from unauthorized users drive a fully tooled agent | Default deny; per-platform and global allowlists; DM pairing codes approved on the CLI |
| API server | Remote code execution via REST | API_SERVER_KEY required for every deployment; loopback bind by default; narrow API_SERVER_CORS_ORIGINS |
| Web dashboard | Reads and writes .env credentials |
Binds 127.0.0.1; a non-loopback bind refuses to start without an auth provider |
| Plugin system | Arbitrary Python in the agent process | Opt-in via plugins.enabled; install-time scanning; project plugins need an env flag |
| Skills / memory / context files | Prompt injection through procedures the agent follows | Project skills need hermes skills trust; memory-entry scanning; context-file injection scanning; write approval for protected files (v0.21.0+) |
| Terminal backends | Destructive or injected shell commands | Hardline blocklist, smart approval mode, user approvals.deny, container isolation |
| Credentials | Theft from disk or leakage into transcripts | .env outside config, credential redaction, optional Bitwarden via hermes secrets, MCP env filtering |
| Session database | History exposure | Local SQLite with OS file permissions; profile isolation |
| Self-evolution | Skill degradation or malicious mutations | Offline pipeline; test/size/semantic gates; human PR review |
Sandboxing¶
Docker Backend Isolation¶
The Docker backend drops all Linux capabilities and adds back only DAC_OVERRIDE, CHOWN, and FOWNER (package managers need them). It sets no-new-privileges, caps processes at 256 (fork-bomb protection), and mounts size-limited /tmp and no-exec /var/tmp. SETUID/SETGID are added only when an s6 entrypoint must drop root. docker_network: false air-gaps the container. The exact flag list is in Reference: Docker Sandbox Security Flags.
The trade-off is explicit in the docs: docker_extra_args is appended last and can silently weaken these defaults, and the default shared container means one session's leftovers are visible to the next.
Singularity / Modal / Daytona / Vercel Isolation¶
Container and cloud backends give namespace or VM isolation from the host. Modal, Daytona, and Vercel Sandbox add provider-level isolation. For all container and cloud backends, dangerous-command checks are skipped because the container itself is the security boundary. Only local and ssh run the approval layer.
Container image trust
The default image (nikolaik/python-nodejs:python3.11-nodejs20) is a community-maintained image. For production deployments, build and host your own image from a trusted base.
Dangerous Command Approval (Local Backend)¶
Commands go through three layers:
- Hardline blocklist (always on, no override, even under
--yolo): filesystem-root wipes, fork bombs,mkfson the live root,ddto a disk, piping untrusted URLs to a root shell. Commands with unparseable shell quoting fail closed. - User deny rules (
approvals.denyglobs), checked before--yoloandmode: off. - Approval mode:
smart(default) asks an auxiliary LLM to auto-approve clearly low-risk commands, auto-deny clearly dangerous ones, and escalate uncertain cases.manualalways prompts;offdisables prompts.
Interactive prompts offer [o]nce, [s]ession, [a]lways, or [d]eny. On messaging platforms the user replies yes/no. Headless contexts (cron, one-shot -q, webhook, API server) deny by default.
Memory and Session Data Protection¶
Session data is stored in local SQLite files under the Hermes home. Protection relies on:
- OS file permissions: the database inherits the permissions of the Hermes data directory
- Profile and session isolation: sessions cannot read each other's state; profiles get separate homes
- WAL mode and atomic writes: concurrent gateway access without corruption;
hermes sessions repair/recoverfor damaged databases
No encryption at rest
SQLite session data is not encrypted at rest. If the host is compromised, all conversation history is readable. For sensitive environments, run Hermes on an encrypted filesystem, prune old sessions (hermes sessions prune), or use per-session containers.
Self-Evolution Safety¶
Two different mechanisms change the agent's own instructions, and they carry different risks:
- In-product learning (background review,
skill_manage, curator) writes directly to the live skill and memory stores by default. The safeguards arememory.write_approval/skills.write_approval(stage every write for review), pinning, non-destructive archival,hermes journey delete/editto prune what was learned, anddisplay.memory_notifications: verboseto see each change. Small models (roughly under 30B parameters) are called out in the docs as prone to claiming saves they never made, or saving wrong assumptions. - Offline self-evolution (DSPy + GEPA) never writes to a running agent. Every variant must pass the full test suite, size limits, and semantic-preservation checks, and it lands only as a human-reviewed PR.
Unattended learning on shared or production agents
With default settings, a gateway bot writes memory and skills from any authorized user's conversations without review. For team or production bots, turn on write_approval for both memory and skills.
Plugin Security¶
Plugins execute arbitrary Python in the agent process with no runtime sandbox. Risks include data exfiltration (reading .env, auth.json, state.db), malicious tool registration, and blocking the agent loop. Mitigations are consent-based rather than technical:
- General plugins are discovered but not loaded until listed in
plugins.enabled - Project plugins also need
HERMES_ENABLE_PROJECT_PLUGINS=true - Plugin installs are scanned, and
hermes security auditchecks plugin requirements against OSV - Gateway event hooks in
~/.hermes/hooks/are trusted by placement and not gated byplugins.enabled: anything placed there is imported at gateway startup
Sources¶
- Architecture
- Agent Loop Internals
- Prompt Assembly
- Gateway Internals
- Tools Runtime
- Persistent Memory
- Memory Providers
- Honcho Memory
- Skills System
- Curator
- Plugins
- Security
- Configuration (terminal backends)
- Hermes Agent Self-Evolution README
- GEPA paper, arXiv 2507.19457 and ICLR 2026 oral listing
- Hermes Agent Releases
- Repository security overview