Skip to content

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:

  1. Generate task ID and append the user message
  2. Build or reuse the cached system prompt (prompt_builder.py)
  3. Preflight compression check: compress when usage passes about 50% of the context window
  4. Build API messages for the active API mode (OpenAI format as-is, Responses input items, or Anthropic via anthropic_adapter.py)
  5. Inject ephemeral prompt layers (budget warnings, context pressure) at call time, not into the cached prefix
  6. Apply prompt-caching markers when talking to Anthropic
  7. Interruptible API call (_interruptible_api_call) on a background thread, abandoned if the user interrupts
  8. If tool calls: dispatch via handle_function_call() (parallel calls run in a ThreadPoolExecutor, interactive tools such as clarify force sequential order), append results, loop back to step 4
  9. 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.
  • /model switches and /reload-mcp are treated as explicit, cache-breaking user actions (/reload-mcp asks 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_interval user 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, every dialecticCadence turns)

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:

  1. Project skills: <repo>/.hermes/skills/ and <repo>/.agents/skills/. They load only after hermes skills trust, because a cloned repo could ship malicious procedures.
  2. Local skills: ~/.hermes/skills/, the source of truth. It holds bundled skills (seeded on install and on each hermes update unless opted out), hub-installed skills, and agent-created skills.
  3. 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

  1. Reflect: GEPA reads execution traces to understand why a candidate failed, not just that it failed, and writes targeted natural-language mutations.
  2. Evaluate: each variant runs against the eval set (synthetic examples or real history from Hermes, Claude Code, and Copilot session stores).
  3. 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.
  4. 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: false switches 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:

  1. Hardline blocklist (always on, no override, even under --yolo): filesystem-root wipes, fork bombs, mkfs on the live root, dd to a disk, piping untrusted URLs to a root shell. Commands with unparseable shell quoting fail closed.
  2. User deny rules (approvals.deny globs), checked before --yolo and mode: off.
  3. Approval mode: smart (default) asks an auxiliary LLM to auto-approve clearly low-risk commands, auto-deny clearly dangerous ones, and escalate uncertain cases. manual always prompts; off disables 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/recover for 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 are memory.write_approval / skills.write_approval (stage every write for review), pinning, non-destructive archival, hermes journey delete/edit to prune what was learned, and display.memory_notifications: verbose to 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 audit checks plugin requirements against OSV
  • Gateway event hooks in ~/.hermes/hooks/ are trusted by placement and not gated by plugins.enabled: anything placed there is imported at gateway startup

Sources