Hermes Agent: Deep Technical Research

A source-code-level analysis of NousResearch's self-evolving AI agent | NousResearch/hermes-agent | MIT | v0.7.0 (2026-04-03)

1. Executive Summary

Hermes Agent by Nous Research is not another IDE copilot. It's a persistent, self-improving AI agent with a unique closed learning loop — it autonomously creates skills from experience, improves them during use, curates persistent memory, searches its own past conversations, and builds a deepening model of its user across sessions.

Key metrics:

  • ~30K GitHub Stars | 242+ Contributors | 7 releases in 5 weeks
  • Core: run_agent.py (9,423 LOC) | cli.py (8,615 LOC)
  • 47 built-in tools | 70+ bundled skills | 14+ messaging platforms | 6 execution backends | 18+ LLM providers

This report is based on direct source code reading, not documentation alone.


2. Architecture Overview

2.1 Three-Tier Design

┌──────────────────────────────────────────────────┐
│              User Interface Layer                 │
│  CLI (cli.py) │ Gateway (gateway/run.py) │ ACP    │
│  Batch Runner │ Cron Scheduler │ API Server       │
├──────────────────────────────────────────────────┤
│              Core Agent Layer                     │
│  AIAgent (run_agent.py) — Central Orchestrator    │
│  ├── _build_system_prompt()   Prompt Assembly     │
│  ├── run_conversation()       Main Agent Loop     │
│  ├── _spawn_background_review() BG Learning Loop  │
│  ├── ContextCompressor        Context Management  │
│  └── IterationBudget          Resource Control    │
├──────────────────────────────────────────────────┤
│           Tool & Execution Layer                  │
│  ToolRegistry (tools/registry.py)                │
│  47 tools × 6 execution backends                 │
│  (Local/Docker/SSH/Modal/Daytona/Singularity)    │
└──────────────────────────────────────────────────┘

2.2 The Core Loop: run_conversation() (run_agent.py:6882)

This is the heart of the system. From source code analysis:

User Message → Surrogate sanitization
             → Restore primary runtime (if fallback occurred last turn)
             → System prompt assembly (cached, built only once)
             → Preflight context compression (if history exceeds threshold)
             → Plugin pre_llm_call hooks
             → External memory provider prefetch
             ╔══════════════════════════════════╗
             ║  Main Loop (while budget > 0)    ║
             ║  1. Check interrupt requests      ║
             ║  2. Consume iteration budget      ║
             ║  3. Inject ephemeral context       ║
             ║  4. Apply Anthropic cache control  ║
             ║  5. Sanitize orphaned tool_calls   ║
             ║  6. API call (with retry logic)    ║
             ║  7. Process tool_calls             ║
             ║  8. Append results to messages     ║
             ║  9. Check context compression need ║
             ╚══════════════════════════════════╝
             → External memory sync + queue_prefetch
             → Background memory/skill review
             → Plugin on_session_end hooks
             → Return result

Notable implementation details:

  1. Thread-safe iteration budget — IterationBudget uses threading.Lock. Parent agents get 90 iterations, child agents get 50.
  2. System prompt built exactly once, cached on _cached_system_prompt. For continued sessions, it's loaded from SessionDB rather than rebuilt — preserving Anthropic prefix cache.
  3. Oversized tool results (>100K chars) are saved to temp files with a 1,500-char inline preview.
  4. Parallel tool execution has strict safety checks — _should_parallelize_tool_batch() validates against a whitelist of parallel-safe tools and checks for path overlaps on file operations.

3. The Four-Layer Memory Architecture (Core Innovation)

This is what sets Hermes apart from every other agent framework.

3.1 Overview

LayerStorageBehaviorRole
Prompt MemoryMEMORY.md + USER.mdAlways injected into system prompt, capped at 3,575 charsSemantic (what matters)
Session SearchSQLite FTS5On-demand retrieval with LLM summarizationEpisodic (what happened)
Skills~/.hermes/skills/ dirsProgressive disclosure — names+summaries by default, full content on demandProcedural (how to do it)
HonchoExternal dialectic enginePassive user modeling across 12 identity layersRelational (who you are)

3.2 Prompt Memory Implementation (tools/memory_tool.py)

The most critical memory layer. Key design decisions from source:

python
class MemoryStore:
    def __init__(self, memory_char_limit=2200, user_char_limit=1375):
        # Total cap: 3,575 chars ≈ ~900 tokens
        # Deliberate constraint: forces curation over accumulation

Frozen Snapshot Pattern:

  • At session start, MEMORY.md and USER.md are loaded and a snapshot is captured for system prompt injection
  • Mid-session writes persist to disk immediately but do not modify the system prompt
  • Reason: preserving prefix cache stability. A precise trade-off between recency and cost efficiency

Security scanning on every write:

python
_MEMORY_THREAT_PATTERNS = [
    (r'ignore\s+(previous|all|above|prior)\s+instructions', "prompt_injection"),
    (r'curl\s+[^
]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)', "exfil_curl"),
    (r'authorized_keys', "ssh_backdoor"),
    # ... more patterns
]

Memory content is injected into the system prompt, making it a critical security boundary. Every write is scanned for injection and exfiltration patterns before persistence.

3.3 Session Search (tools/session_search_tool.py)

FTS5-powered retrieval with LLM summarization:

FTS5 search → Group by session → Top 3 unique sessions
→ Load conversation → Truncate to ~100K chars around matches
→ Auxiliary LLM summarization → Return structured summaries

This retrieve-then-summarize pattern avoids stuffing raw conversation history into the context window.

3.4 Periodic Memory/Skill Nudge Mechanism

The nudge system is the engine of the learning loop:

python
# run_agent.py
self._memory_nudge_interval = 10  # Every 10 user turns → memory review
self._skill_nudge_interval = 10   # Every 10 tool iterations → skill review

When triggered, _spawn_background_review():

  1. Creates a full AIAgent fork (shared memory store, own iteration budget)
  2. Uses current conversation history as context
  3. Appends a review prompt as the "user message"
  4. Runs in a background daemon thread — never blocks user interaction
  5. The review agent has access to memory and skill_manage tools
  6. On completion, surfaces a compact summary: "💾 Memory updated · Skill created"

4. Skills System (Procedural Memory)

4.1 Skill Structure

~/.hermes/skills/
├── systematic-debugging/
│   └── SKILL.md          # YAML frontmatter + Markdown instructions
├── software-development/
│   ├── test-driven-development/
│   │   └── SKILL.md
│   └── subagent-driven-development/
│       └── SKILL.md
└── category/skill-name/
    ├── SKILL.md
    ├── references/       # Reference materials
    ├── templates/        # Template files
    ├── scripts/          # Scripts
    └── assets/           # Assets

Skills use the agentskills.io open standard — a strategic bet on cross-agent portability.

4.2 Progressive Disclosure

Skills are NOT loaded all at once:

python
# agent/prompt_builder.py - build_skills_system_prompt()
# Default: only skill names + description summaries (dozens of bytes per skill)
# Full content loaded only when the agent determines relevance
# Token usage stays flat regardless of skill count (70+ skills, no bloat)

4.3 Self-Improving Skills

Skills are mutable. The agent can modify them through 6 actions:

python
# tools/skill_manager_tool.py
# Actions: create, edit, patch, delete, write_file, remove_file
# Default: patch (targeted find-and-replace) over edit (full rewrite)

Trigger conditions (from SKILLS_GUIDANCE):

  • Complex task completion (5+ tool calls)
  • Tricky error resolution
  • Non-obvious workflow discovery

Security: Agent-created skills undergo the same security scanning as community skills (skills_guard.py), detecting exfiltration, injection, destructive commands, and persistence patterns.


5. Tool System

5.1 Registry Pattern (tools/registry.py)

python
class ToolRegistry:
    """Singleton registry collecting schemas + handlers from tool files"""

    def register(self, name, toolset, schema, handler, check_fn=None, ...):
        """Called at module-import time by each tool file"""

    def get_definitions(self, tool_names, quiet=False):
        """Returns OpenAI-format tool schemas (only tools passing check_fn)"""

    def dispatch(self, name, args, **kwargs):
        """Execute tool handler by name (auto-bridges async handlers)"""

Self-registration decouples tool implementations from orchestration logic. Each tool file calls registry.register() at import time — no central manifest to maintain.

5.2 47 Built-in Tools (20 Toolsets)

ToolsetKey ToolsDescription
terminalterminalCommand execution across 6 backends
browserbrowser_* (11 tools)Playwright + Camofox anti-detection browser
webweb_search, web_extractSearch and content extraction
fileread_file, write_file, patch, search_filesFile operations
code_executionexecute_codeSandboxed code execution
delegationdelegate_taskSubagent spawning
memorymemoryPersistent memory management
skillsskills_list, skill_view, skill_manageSkill system
session_searchsession_searchHistorical session search
visionvision_analyzeImage analysis
image_generationimage_generateImage generation (fal.ai)
ttstext_to_speechTTS (edge-tts / ElevenLabs)
mcpdynamicMCP protocol extension
croncron_*Scheduled tasks

5.3 Dangerous Command Approval System (tools/approval.py)

python
DANGEROUS_PATTERNS = [
    (r'\brm\s+-[^\s]*r', "recursive delete"),
    (r'\bchmod\s+(-[^\s]*\s+)*(777|666)', "world-writable permissions"),
    (r'\bDROP\s+(TABLE|DATABASE)\b', "SQL DROP"),
    (r':\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:', "fork bomb"),
    (r'\b(curl|wget)\b.*\|\s*(ba)?sh\b', "pipe remote content to shell"),
    # ... 30+ patterns
]

Key features:

  • Command normalization: Strips ANSI escapes, null bytes, Unicode fullwidth characters before matching — prevents bypass via encoding tricks
  • Three-tier approval: once / session / always (permanent allowlist in config.yaml)
  • Gateway async approval: Threading.Event queues enable cross-thread blocking approval in gateway mode
  • Smart approval: Optional auxiliary LLM evaluates low-risk commands for auto-approval

6. Execution Backends

6.1 Strategy Pattern (tools/environments/base.py)

python
class BaseEnvironment(ABC):
    """Common interface for all Hermes execution backends"""

    @abstractmethod
    def execute(self, command, cwd="", *, timeout=None, stdin_data=None) -> dict:
        """Execute command, return {"output": str, "returncode": int}"""

    @abstractmethod
    def cleanup(self):
        """Release backend resources"""

Six backends behind a single interface, switchable via configuration:

BackendUse Case
LocalDirect host execution (default, fastest)
DockerIsolated containers (read-only root, dropped capabilities)
SSHRemote server execution
ModalServerless cloud sandboxes (hibernates when idle)
DaytonaManaged workspace environments
SingularityHPC/research environments

7. Messaging Gateway (14+ Platforms)

A single gateway process manages adapters for: Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Mattermost, Email (IMAP/SMTP), SMS, DingTalk, Feishu/Lark, WeCom, Home Assistant, and Webhooks.

Conversation context persists across platforms — start on Telegram, continue on Discord, finish via Email.

Key gateway hardening in v0.7.0: race condition fixes, flood control, stuck session recovery, approval routing, and compression death spiral prevention.


8. Prompt Caching Strategy

8.1 Anthropic Prefix Caching (agent/prompt_caching.py)

python
def apply_anthropic_cache_control(api_messages, cache_ttl="5m"):
    """system_and_3 strategy: system prompt + last 3 non-system messages"""
    # Anthropic allows max 4 cache_control breakpoints
    # 1. System prompt (stable across all turns)
    # 2-4. Last 3 non-system messages (rolling window)

Why is the system prompt never modified mid-session?

This is a hard architectural rule, enforced throughout the codebase:

  • _cached_system_prompt is built once, reused thereafter
  • Continued sessions load stored system prompts from SessionDB (not rebuilt)
  • External memory context injected into user messages, not system prompt
  • Plugin context also injected into user messages

Result: ~75% reduction in input token costs.


9. Context Compression (agent/context_compressor.py)

python
class ContextCompressor:
    """Algorithm:
    1. Prune old tool results (cheap, no LLM call)
    2. Protect head messages (system prompt + first exchange)
    3. Protect tail messages by token budget (~20K tokens)
    4. Summarize middle turns with structured LLM prompt
    5. Iteratively update previous summary on subsequent compactions
    """

Key parameters:

  • threshold_percent = 0.50 — triggers at 50% context usage
  • _SUMMARY_RATIO = 0.20 — summary gets 20% of compressed content's token count
  • _SUMMARY_TOKENS_CEILING = 12,000 — absolute cap on summary tokens
  • Structured summary template: Goal, Progress, Decisions, Files, Next Steps

10. Subagent Delegation (tools/delegate_tool.py)

python
DELEGATE_BLOCKED_TOOLS = frozenset([
    "delegate_task",   # No recursive delegation
    "clarify",         # No user interaction
    "memory",          # No writes to shared MEMORY.md
    "send_message",    # No cross-platform side effects
    "execute_code",    # Children should reason step-by-step
])

MAX_CONCURRENT_CHILDREN = 3
MAX_DEPTH = 2  # parent(0) → child(1) → grandchild rejected
DEFAULT_MAX_ITERATIONS = 50

Design: fresh context (no parent history), restricted toolsets, independent iteration budget. Parent only sees the delegation call and summary result — never intermediate steps.


11. Security Architecture

11.1 Defense in Depth

LayerMechanismImplementation
InputPrompt injection scanningprompt_builder.py — 10 threat patterns + invisible char detection
MemoryContent scanning on writememory_tool.py — injection/exfil pattern detection
SkillsPre-install security scanskills_guard.py — trust levels + threat patterns
CommandsDangerous command approvalapproval.py — 30+ patterns + command normalization
ToolsOutput size limits100K char threshold, overflow saved to file
ExecutionContainer isolationDocker: read-only root, dropped capabilities
OutputSecret exfiltration detectionBrowser URLs and LLM responses scanned

11.2 Invisible Character Detection

python
_CONTEXT_INVISIBLE_CHARS = {
    '\u200b',  # Zero-width space
    '\u200c',  # Zero-width non-joiner
    '\u200d',  # Zero-width joiner
    '\u2060',  # Word joiner
    '\ufeff',  # Zero-width no-break space (BOM)
    '\u202a'-'\u202e',  # Bidi override characters
}

These characters can be used to hide prompt injection payloads. They're detected and blocked in context files, memory entries, and skill content.


12. Credential Pool (agent/credential_pool.py)

python
STRATEGY_FILL_FIRST = "fill_first"
STRATEGY_ROUND_ROBIN = "round_robin"
STRATEGY_RANDOM = "random"
STRATEGY_LEAST_USED = "least_used"  # default

EXHAUSTED_TTL_429_SECONDS = 60 * 60      # Rate limit: 1 hour cooldown
EXHAUSTED_TTL_DEFAULT_SECONDS = 24 * 60 * 60  # Other failures: 24 hour cooldown

Multiple API keys per provider with thread-safe least_used rotation and automatic 401 failover. Production-grade credential management for high-throughput deployments.


13. Tech Stack

Core Language: Python 3.11+ (93.4%)
Package Manager: uv (Astral)
Database: SQLite + FTS5
CLI UI: Rich + prompt_toolkit + KawaiiSpinner
LLM SDKs: openai + anthropic
Web: httpx + requests + exa-py + firecrawl-py
Browser: Playwright + Camoufox
TTS: edge-tts (free) + ElevenLabs (premium)
STT: faster-whisper
Config: YAML (pyyaml)
MCP: mcp >= 1.2.0
Testing: pytest (3,000+ test cases)
Build: Nix Flake (reproducible builds)

14. Competitive Analysis

DimensionHermes AgentOpenClaw (OpenHands)Claude Code
FocusSelf-evolving learning agentBroad ecosystem integrationDeveloper coding assistant
Memory4-layer (prompt/episodic/procedural/dialectic)Basic contextFile-backed notes
Skillsagentskills.io portable standard, self-creating+improvingPlugin-basedTool-use only
Messaging14 platforms, single gatewayLimitedTelegram/Discord
Execution6 backendsPrimarily localLocal
CostLLM API only; runs on $5 VPS$59/mo or self-hostSubscription
SecurityCommand approval + injection scanning + Docker sandboxStandardPermission prompts
Open SourceMITOpen sourceProprietary

15. Key Design Insights

15.1 The 3,575-Character Prompt Memory Cap

memory_char_limit=2200 + user_char_limit=1375 = 3,575 characters ≈ ~900 tokens.

Why so small? This is a deliberate constraint that forces curation over accumulation. Unbounded growth turns memory into a garbage dump. The cap forces the agent to retain only genuinely valuable information — making every character count.

15.2 Frozen Snapshot vs. Real-Time Updates

System prompt memory is a snapshot from session start. Mid-session writes update disk but don't change the active system prompt. This sacrifices memory recency for 75% cache cost savings. For most use cases, this is the right trade-off.

15.3 Background Review, Not Inline Review

Memory and skill review runs in background daemon threads via _spawn_background_review(). Users never wait for review to complete. Review results only take effect in the next session (due to frozen snapshot). This is architecturally clean: the learning loop is decoupled from the task execution loop.

15.4 The agentskills.io Bet

Skills use YAML frontmatter + Markdown, portable across compatible agent frameworks. If widely adopted, this becomes the "npm for agent skills" — a significant ecosystem play.

15.5 Full-Chain Prompt Injection Defense

From context file loading → memory writes → skill installation → command execution, every layer has independent injection detection. This isn't a security afterthought — it's woven into the architecture from the ground up.


16. Conclusion

Hermes Agent's core innovation is not any single feature, but a complete self-evolution loop:

Task Execution → Experience Accumulation → Memory Curation → Skill Creation
       ↑                                                          ↓
       ← ← ← ←  Skill Improvement  ← Skill Retrieval  ← ← ← ←

Every component of this loop has careful engineering:

  • Memory has capacity constraints and security scanning
  • Skills have progressive disclosure and self-improvement
  • Review runs in background without blocking users
  • Caching strategy permeates the entire architecture

7 releases in 5 weeks, 30K stars, 242 contributors — this project has found a real need in the open-source AI agent space: a general-purpose agent framework that isn't tied to a specific IDE, isn't locked to a specific LLM, and actually learns from use.


Based on v0.7.0 source code (commit 2026-04-03) deep reading analysis | Analysis date: 2026-04-07

Linked from

Comments (0)

No comments yet

imfht
April 7, 2026