> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 05-2 Hermes-Agent Anatomy: Nous Research's Minimalist Agent

> From three-layer prompt assembly to SQLite memory curation, dissect the architectural decisions and trade-offs of Hermes-Agent so you can make an informed choice between 'one self serving multiple channels' and 'multiple selves each serving different contexts'.

export const FrameworkMap = ({lang = "zh", title, layers = []}) => {
  const t = lang === "en" ? {
    hint: "Click a module to see details",
    layer: "Layer",
    module: "Module",
    responsibilities: "Responsibilities",
    path: "Key path",
    relations: "Connected to",
    noSelect: "Select a module above"
  } : {
    hint: "點模組卡片查看職責與關係",
    layer: "層",
    module: "模組",
    responsibilities: "職責",
    path: "關鍵路徑",
    relations: "連結到",
    noSelect: "點上方模組查看詳細說明"
  };
  const [selected, setSelected] = useState(null);
  const safe = Array.isArray(layers) ? layers : [];
  const PALETTE = {
    blue: {
      base: "#5b6c8f",
      bg: "#5b6c8f18",
      border: "#5b6c8f55",
      label: "#46587a"
    },
    purple: {
      base: "#8a6f8e",
      bg: "#8a6f8e18",
      border: "#8a6f8e55",
      label: "#6f5673"
    },
    yellow: {
      base: "#c9a35b",
      bg: "#c9a35b18",
      border: "#c9a35b55",
      label: "#9a7a3c"
    },
    green: {
      base: "#8a9a7b",
      bg: "#8a9a7b18",
      border: "#8a9a7b55",
      label: "#6a7a5c"
    },
    teal: {
      base: "#6f9290",
      bg: "#6f929018",
      border: "#6f929055",
      label: "#527472"
    },
    rose: {
      base: "#ab7269",
      bg: "#ab726918",
      border: "#ab726955",
      label: "#8e564e"
    },
    amber: {
      base: "#b9835c",
      bg: "#b9835c18",
      border: "#b9835c55",
      label: "#96683f"
    }
  };
  const ROTATION = ["blue", "purple", "yellow", "green", "teal", "rose"];
  const getColor = (layer, index) => {
    const key = layer.color && PALETTE[layer.color] ? layer.color : ROTATION[index % ROTATION.length];
    return PALETTE[key];
  };
  const sel = (() => {
    if (!selected) return null;
    for (let li = 0; li < safe.length; li++) {
      const layer = safe[li];
      const m = layer.modules.find(m => m.id === selected);
      if (m) return {
        module: m,
        layerLabel: layer.label,
        color: getColor(layer, li)
      };
    }
    return null;
  })();
  const css = `
.fm-root{--fm-bg:#FAF8F3;--fm-surface2:rgba(0,0,0,0.022);
  --fm-border:rgba(0,0,0,0.08);--fm-text:#2b2722;--fm-dim:#6f6a62;
  --fm-faint:#9a9490;--fm-accent:#bf7551;--fm-card-bg:#fff;
  border:1px solid var(--fm-border);border-radius:14px;background:var(--fm-bg);
  color:var(--fm-text);overflow:hidden;font-family:inherit;}
.dark .fm-root{--fm-bg:#1b1a18;--fm-surface2:rgba(255,255,255,0.025);
  --fm-border:rgba(255,255,255,0.07);--fm-text:#e7e3da;
  --fm-dim:#a8a299;--fm-faint:#6a6560;--fm-card-bg:rgba(255,255,255,0.04);}
.fm-head{padding:13px 18px 11px;display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--fm-border);flex-wrap:wrap;}
.fm-head-ic{color:var(--fm-accent);flex-shrink:0;}
.fm-head-title{font-size:14px;font-weight:700;letter-spacing:0.01em;flex:1;}
.fm-head-hint{font-size:11.5px;color:var(--fm-faint);}
.fm-layers{padding:14px 16px 10px;display:flex;flex-direction:column;gap:12px;}
.fm-layer{}
.fm-layer-label{font-size:10.5px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  margin-bottom:7px;display:flex;align-items:center;gap:6px;}
.fm-layer-label::after{content:"";flex:1;height:1px;opacity:0.4;}
.fm-modules{display:flex;flex-wrap:wrap;gap:8px;}
.fm-module{display:flex;flex-direction:column;gap:3px;padding:9px 13px;border-radius:9px;
  border:1px solid var(--fm-border);background:var(--fm-card-bg);cursor:pointer;
  transition:border-color .15s,background .15s,box-shadow .15s;text-align:left;
  min-width:120px;max-width:220px;flex:1 1 120px;}
.fm-mod-name{font-size:13px;font-weight:600;line-height:1.3;color:var(--fm-text);}
.fm-mod-summary{font-size:11.5px;line-height:1.5;color:var(--fm-dim);}
.fm-mod-path{font-size:10.5px;font-family:ui-monospace,"Cascadia Code","Consolas",monospace;
  color:var(--fm-faint);margin-top:2px;word-break:break-all;}
.fm-arrow{display:flex;justify-content:center;padding:2px 0;}
.fm-panel{border-top:1px solid var(--fm-border);padding:14px 18px;background:var(--fm-surface2);min-height:80px;}
.fm-panel-empty{display:flex;align-items:center;justify-content:center;height:60px;
  font-size:12.5px;color:var(--fm-faint);}
.fm-panel-layer{font-size:10.5px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;
  margin-bottom:5px;}
.fm-panel-name{font-size:16px;font-weight:700;margin-bottom:6px;line-height:1.3;}
.fm-panel-path{font-size:11px;font-family:ui-monospace,"Cascadia Code","Consolas",monospace;
  color:var(--fm-faint);margin-bottom:10px;word-break:break-all;}
.fm-panel-sec{font-size:10.5px;font-weight:700;letter-spacing:0.05em;text-transform:uppercase;
  color:var(--fm-dim);margin-bottom:5px;margin-top:10px;}
.fm-panel-detail{font-size:13.5px;line-height:1.65;color:var(--fm-dim);}
.fm-rels{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px;}
.fm-rel{font-size:11.5px;padding:3px 9px;border-radius:20px;}
@media (max-width:600px){
  .fm-modules{flex-direction:column;}
  .fm-module{max-width:100%;}
  .fm-head-hint{display:none;}
}
`;
  return <div className="fm-root">
      <style>{css}</style>
      <div className="fm-head">
        <svg className="fm-head-ic" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <rect x="2" y="3" width="20" height="5" rx="2" />
          <rect x="2" y="10" width="20" height="5" rx="2" />
          <rect x="2" y="17" width="20" height="5" rx="2" />
        </svg>
        {title && <span className="fm-head-title">{title}</span>}
        <span className="fm-head-hint">{t.hint}</span>
      </div>

      <div className="fm-layers">
        {safe.map((layer, li) => {
    const c = getColor(layer, li);
    return <div className="fm-layer" key={layer.id || li}>
              <div className="fm-layer-label" style={{
      color: c.label
    }}>
                <span>{layer.label}</span>
                <span style={{
      flex: 1,
      height: "1px",
      background: c.border,
      display: "block"
    }} />
              </div>
              <div className="fm-modules">
                {(layer.modules || []).map(mod => {
      const isActive = selected === mod.id;
      return <button key={mod.id} type="button" className="fm-module" style={isActive ? {
        borderColor: c.base,
        background: c.bg,
        boxShadow: "0 0 0 3px " + c.base + "22"
      } : {}} onMouseEnter={e => {
        if (!isActive) {
          e.currentTarget.style.borderColor = c.base;
          e.currentTarget.style.background = c.bg;
        }
      }} onMouseLeave={e => {
        if (!isActive) {
          e.currentTarget.style.borderColor = "";
          e.currentTarget.style.background = "";
        }
      }} onClick={() => setSelected(selected === mod.id ? null : mod.id)}>
                      <div className="fm-mod-name" style={isActive ? {
        color: c.label
      } : {}}>{mod.name}</div>
                      {mod.summary && <div className="fm-mod-summary">{mod.summary}</div>}
                      {mod.path && <div className="fm-mod-path">{mod.path}</div>}
                    </button>;
    })}
              </div>
              {li < safe.length - 1 && <div className="fm-arrow">
                  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={getColor(safe[li + 1] || layer, li + 1).base} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" style={{
      opacity: 0.5
    }}>
                    <path d="M12 5v14M5 15l7 7 7-7" />
                  </svg>
                </div>}
            </div>;
  })}
      </div>

      <div className="fm-panel">
        {sel ? <div>
            <div className="fm-panel-layer" style={{
    color: sel.color.label
  }}>{t.layer}: {sel.layerLabel}</div>
            <div className="fm-panel-name">{sel.module.name}</div>
            {sel.module.path && <div className="fm-panel-path">{sel.module.path}</div>}
            {sel.module.detail && <div>
                <div className="fm-panel-sec">{t.responsibilities}</div>
                <div className="fm-panel-detail">{sel.module.detail}</div>
              </div>}
            {sel.module.relations && sel.module.relations.length > 0 && <div>
                <div className="fm-panel-sec">{t.relations}</div>
                <div className="fm-rels">
                  {sel.module.relations.map((r, i) => <span className="fm-rel" key={i} style={{
    background: sel.color.bg,
    color: sel.color.label,
    border: "1px solid " + sel.color.border
  }}>{r.to}{r.label ? " — " + r.label : ""}</span>)}
                </div>
              </div>}
          </div> : <div className="fm-panel-empty">{t.noSelect}</div>}
      </div>
    </div>;
};

export const LearnerPrimer = ({items = [], lang = "zh"}) => {
  const t = lang === "en" ? {
    title: "Before this unit, be honest with yourself",
    sub: "If you can't answer these, that gap is exactly what this unit closes."
  } : {
    title: "讀這個單元前，先誠實面對",
    sub: "這幾題答不出來，正是這個單元要替你補的洞。"
  };
  const css = `
  .lp-root{--lp-bg:#FAF7F1;--lp-surface:rgba(191,117,81,0.05);--lp-border:rgba(0,0,0,0.09);--lp-edge:rgba(191,117,81,0.42);--lp-text:#2b2722;--lp-dim:#6f6a62;--lp-accent:#bf7551;border:1px solid var(--lp-border);border-left:3px solid var(--lp-edge);border-radius:13px;background:var(--lp-bg);color:var(--lp-text);overflow:hidden;margin:1.25rem 0;}
  .dark .lp-root{--lp-bg:#1b1a18;--lp-surface:rgba(207,138,104,0.07);--lp-border:rgba(255,255,255,0.08);--lp-edge:rgba(207,138,104,0.5);--lp-text:#e7e3da;--lp-dim:#a8a299;--lp-accent:#cf8a68;}
  .lp-head{display:flex;align-items:center;gap:9px;padding:13px 18px 11px;border-bottom:1px solid var(--lp-border);background:var(--lp-surface);}
  .lp-ic{color:var(--lp-accent);flex-shrink:0;}
  .lp-htx{display:flex;flex-direction:column;gap:1px;min-width:0;}
  .lp-title{font-size:14px;font-weight:650;line-height:1.3;letter-spacing:.01em;}
  .lp-sub{font-size:12px;color:var(--lp-dim);line-height:1.4;}
  .lp-list{list-style:none;margin:0;padding:10px 18px 14px;display:flex;flex-direction:column;gap:0;}
  .lp-item{display:flex;align-items:baseline;gap:11px;padding:7px 0;font-size:14px;line-height:1.6;border-top:1px solid var(--lp-border);}
  .lp-item:first-child{border-top:none;}
  .lp-mark{flex-shrink:0;color:var(--lp-accent);font-size:13px;font-weight:700;line-height:1.55;font-variant-numeric:tabular-nums;opacity:.85;}
  .lp-q{flex:1 1 0;min-width:0;color:var(--lp-text);}
  @media (max-width:620px){.lp-head{padding:12px 14px 10px;}.lp-list{padding:8px 14px 12px;}}
  `;
  return <div className="lp-root">
      <style>{css}</style>
      <div className="lp-head">
        <svg className="lp-ic" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" /></svg>
        <span className="lp-htx">
          <span className="lp-title">{t.title}</span>
          <span className="lp-sub">{t.sub}</span>
        </span>
      </div>
      <ul className="lp-list">
        {items.map((q, i) => <li className="lp-item" key={i}>
            <span className="lp-mark">{String(i + 1).padStart(2, "0")}</span>
            <span className="lp-q">{q}</span>
          </li>)}
      </ul>
    </div>;
};

<LearnerPrimer
  lang="en"
  items={[
"Hermes-Agent is a framework, not a model -- don't conflate the two Hermeses.",
"Three-layer prompt: stable changes least; volatile is a frozen snapshot.",
"Editing SOUL.md mid-session has no effect on the current session -- restart required.",
"Sub-agents do not inherit the main persona; they use hardcoded DEFAULT_AGENT_IDENTITY.",
"The default SOUL.md is auto-planted on first run -- read default_soul.py before changing it.",
"SQLite holds the session-summary basis -- delete it and you lose the history.",
"Frozen snapshots exist to protect prefix cache cost; any OpenAI-compatible API works.",
]}
/>

<Info>
  **What this unit solves**

  Hermes-Agent is an open-source AI agent framework maintained by Nous Research, positioned as "the agent that grows with you." It is not tied to any inference backend and does not require the Hermes model family. A set of Markdown files defines the agent's persona, memory, and instructions, and a three-layer system prompt assembly injects those files in a disciplined way. This unit dissects its origins, core technology, system structure, and Markdown file design so you can see the deliberate choices and trade-offs behind the "minimalist shell."
</Info>

## Learning objectives

* [ ] Explain Hermes-Agent's origins and positioning: it is an agent framework, not an inference model, maintained by Nous Research under the MIT license
* [ ] Describe its core architecture: the `AIAgent` main loop, three-layer system prompt assembly, multi-backend tool dispatch, and SQLite state storage
* [ ] State the role of `SOUL.md`, `MEMORY.md`, `USER.md`, and `AGENTS.md` in prompt assembly -- their layers and responsibilities
* [ ] Identify the trade-offs of the minimalist Markdown file design: easy to understand and externalize, but lacking structured version control under high concurrency or multi-agent scenarios
* [ ] Summarize the essence of this system in one everyday analogy
* [ ] Compare it directly against [05-1 OpenClaw](/en/code-agent/case-studies/openclaw)'s multi-file design

***

## 1. Origins and positioning

Hermes-Agent is open-sourced and maintained by Nous Research, positioned as "The agent that grows with you" (official repository \[1] as of 2026-05). It is a new framework developed independently by Nous Research -- not derived from OpenClaw. The `hermes claw migrate` command is a migration tool for existing OpenClaw users to move their configuration, memory, and skills; the two are independent projects from different organizations (official documentation \[2] as of 2026-05).

A few common misconceptions to clear up first:

* **It is not the Hermes model family.** Hermes-3-405B, Hermes-3-70B, and similar models are open-source LLMs trained by Nous Research. They are separate products from the Hermes-Agent framework. Hermes-Agent is not bound to any inference backend; it supports OpenRouter, Anthropic, OpenAI, Google, vLLM, llama.cpp, and any other OpenAI-compatible API. The Hermes models are simply Nous Portal's default preferred endpoint.
* **It is not a lightweight SDK.** Despite the clean Markdown file interface, it is positioned as a full-featured agentic harness that "can run on a \$5 VPS or a GPU cluster and interact across Telegram, Discord, and other platforms." It ships with sub-agent delegation, SQLite state storage, and cross-session memory.
* **The license is MIT** (as of 2026-05, per the repository LICENSE file): commercial use, modification, and closed-source redistribution are all permitted.

<Note>
  **Naming clarification**

  In Nous Research's naming scheme, "Hermes" refers to three distinct things simultaneously: the Hermes model family (LLM), Hermes-Agent (the agent framework), and Nous Portal (the inference subscription service). This unit covers the second. The full name "Hermes-Agent" is used whenever confusion is possible.
</Note>

## 2. Core technology

Hermes-Agent's technology stack can be broken into five axes:

| Axis              | Implementation                                               | Source / notes                                                                                                        |
| ----------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| Main loop         | `AIAgent` class in `run_agent.py`                            | Official documentation describes the class as "main loop"; exact API and method signatures per official documentation |
| API mode          | `chat_completion` / `codex_responses` / `anthropic` (native) | Selected automatically based on inference backend                                                                     |
| Tool system       | 70+ tools, 28 toolset groups                                 | Concentrated in `model_tools.py` and `toolsets.py`                                                                    |
| State persistence | SQLite + FTS5                                                | `hermes_state.py`, supports cross-session search and LLM-summary recall                                               |
| Skill loading     | `skills/` directory, `SKILL.md` format                       | Progressive disclosure: Level 0 loads only the index; Level 1 loads full content                                      |

Compared to OpenClaw's "many Markdown files plus multi-channel routing" design, Hermes-Agent's minimalism shows in three places:

1. **Prompt assembly is three layers, not scattered ad hoc.** `agent/prompt_builder.py` assembles the stable / context / volatile layers into a system prompt, with explicit slot numbers and injection strategies for each layer.
2. **Inference backend is fully pluggable.** Any service that accepts an OpenAI-compatible API can be connected -- a deliberate choice for "no single-vendor lock-in."
3. **State is SQLite, not plain files.** OpenClaw's memory relies mainly on `MEMORY.md` + `memory/YYYY-MM-DD.md`; Hermes-Agent adds a SQLite + FTS5 layer supporting cross-session full-text search and LLM-summary recall.

<Tip>
  **Why three-layer prompt assembly**

  Splitting the prompt into layers rather than writing one large block is motivated by the different "rate of change" and "cache hit requirements" of each layer: the stable layer (persona) almost never changes and has the highest prefix cache hit rate; the context layer (project instructions) changes with CWD; the volatile layer (user and memory) is re-fetched each session. Layering means only the parts that changed need to be re-fetched. Hermes-Agent further "freezes" the volatile layer into a snapshot at session start -- memory operations during the session only write to disk and take effect in the next session (Prompt Assembly documentation \[3] as of 2026-05). The trade-off of this design is discussed in [Section 4](#4-custom-markdown-files-the-role-of-soulmd-and-others).
</Tip>

<FrameworkMap
  lang="en"
  title="Hermes-Agent: Three-Layer System Prompt Assembly"
  layers={[
{
  id: "stable",
  color: "teal",
  label: "stable layer — persona (rarely changes)",
  modules: [
    {
      id: "soul",
      name: "SOUL.md",
      path: "~/.hermes/SOUL.md ($HERMES_HOME)",
      summary: "Agent persona and identity, loaded from $HERMES_HOME regardless of CWD",
      detail: "Defines who the agent is: name, personality, tone, and behavior boundaries. Loaded from $HERMES_HOME regardless of CWD, so it remains consistent across all projects. The default SOUL.md is auto-planted on first run; read default_soul.py before editing it. Rarely changes, giving it the highest prefix cache hit rate of the three layers.",
      relations: [{ to: "AGENTS.md", label: "persona vs instructions" }],
    },
  ],
},
{
  id: "context",
  color: "blue",
  label: "context layer — project instructions (changes with CWD)",
  modules: [
    {
      id: "agents",
      name: "AGENTS.md",
      path: "CWD → git root walk",
      summary: "Project instructions and conventions, discovered by walking up from CWD to git root",
      detail: "Contains project-specific instructions, conventions, and routing rules. Hermes-Agent walks up from the current working directory to the git root and uses the first matching file. Also accepts .hermes.md and CLAUDE.md. This layer changes as you switch projects, so it gets re-fetched on CWD change while the stable layer stays cached.",
      relations: [{ to: "SOUL.md", label: "instructions vs persona" }, { to: "Volatile snapshot", label: "injected after" }],
    },
  ],
},
{
  id: "volatile",
  color: "purple",
  label: "volatile layer — frozen snapshot (re-fetched each session)",
  modules: [
    {
      id: "memory",
      name: "MEMORY.md",
      path: "~/.hermes/memories/MEMORY.md",
      summary: "Long-term memory distillation, 2,200 character limit (~800 tokens)",
      detail: "Stores environmental facts, conventions, and things the agent has learned. Capped at 2,200 characters; the agent must select and consolidate rather than accumulate. Frozen into a snapshot at session start; writes during the session only land on disk and take effect next session.",
      relations: [{ to: "USER.md", label: "injected together" }, { to: "state.db", label: "separate from full log" }],
    },
    {
      id: "user",
      name: "USER.md",
      path: "~/.hermes/memories/USER.md",
      summary: "User preferences, communication style, expectations, 1,375 character limit (~500 tokens)",
      detail: "Records your preferred name, communication style, and working preferences. Written by the agent itself using the memory tool during interactions. Capped at 1,375 characters. Frozen into the volatile snapshot at session start.",
      relations: [{ to: "MEMORY.md", label: "injected together" }],
    },
  ],
},
{
  id: "infra",
  color: "amber",
  label: "Infrastructure layer — state and tooling",
  modules: [
    {
      id: "statedb",
      name: "state.db",
      path: "~/.hermes/state.db",
      summary: "SQLite + FTS5 complete session history, searchable via session_search tool",
      detail: "Stores all CLI and messaging sessions in full. Searchable with the session_search tool to retrieve past raw messages. Independent from MEMORY.md and USER.md: this is a detailed log, not the curated notes. Deleting it loses all history.",
      relations: [{ to: "MEMORY.md", label: "log vs curated notes" }],
    },
    {
      id: "curator",
      name: "curator.py",
      path: "hermes/memory/curator.py",
      summary: "Memory curation logic: agent-driven write, replace, remove",
      detail: "Implements the memory tool that the agent calls to update MEMORY.md and USER.md. There is no background auto-summarization; the agent judges during interaction what is worth remembering and actively curates the two files through this module.",
      relations: [{ to: "MEMORY.md", label: "writes to" }, { to: "USER.md", label: "writes to" }],
    },
    {
      id: "prompt-builder",
      name: "prompt_builder.py",
      path: "hermes/prompt/prompt_builder.py",
      summary: "Assembles the three layers into a single system prompt per session",
      detail: "Reads SOUL.md, resolves AGENTS.md via CWD walk, and freezes MEMORY.md + USER.md into the volatile snapshot. Concatenates all three and passes the assembled prompt to the model API call. Prefix cache stability depends on this layer doing the assembly in a consistent order.",
      relations: [{ to: "SOUL.md", label: "reads" }, { to: "AGENTS.md", label: "resolves" }, { to: "MEMORY.md", label: "freezes snapshot" }],
    },
  ],
},
]}
/>

## 2.5 Why it can "grow with you": the agent curates its own memory and re-injects it next session

The "grows with you" mechanism is not background auto-summarization. It is **the agent actively curating its own memory** (Memory documentation \[4] as of 2026-06). Unpacked:

1. **Two capped memory files:** `MEMORY.md` (2,200 character limit, roughly 800 tokens) records "environmental facts, conventions, and things learned"; `USER.md` (1,375 character limit, roughly 500 tokens) records "your preferences, communication style, and expectations." Both live in `~/.hermes/memories/`.
2. **Written by the agent itself via the `memory` tool:** there is no background process auto-distilling content. The agent judges during interaction that "this is worth remembering" and actively adds, replaces, or removes entries in the two files above. The character limit is a key design choice: it forces the agent to select and consolidate rather than accumulate indefinitely, so "growth" is curated compression, not a running log.
3. **Re-injected at the next session:** at session start, both files are frozen into a snapshot and injected into the volatile layer (see the three-layer assembly above). The agent then opens with "what I remembered about you and this work last time," producing more contextually appropriate behavior.

A separate **SQLite + FTS5 complete history** (`~/.hermes/state.db`) stores all CLI and messaging sessions and can be queried with the `session_search` tool to retrieve past raw messages. Note that this and the "active memory" above are **two independent systems:** SQLite is a searchable detailed log; `MEMORY.md` and `USER.md` are the agent's curated long-term notes. The former does not automatically distill into the latter.

Key inference: growth happens **between sessions**, not within them -- a direct consequence of volatile freezing. One session is "read last session's memory, do work, write what's worth keeping to disk"; repeat next time. This also explains why `SOUL.md` (persona) stays constant while `MEMORY.md` and `USER.md` (learning) grow: persona is a constant you define upfront; growth only occurs in the memory layer.

Memory is one arm of growth. The other is **skill self-evolution:** the agent can write its own skills, and `curator.py` maintains the lifecycle of these "agent-authored skills" in the background, tracking usage frequency and transitioning rarely-used skills from active to stale (default 30 days) then to archived (sealed at 90 days). It periodically dispatches a helper model to review, proposing consolidations of overlapping skills or repairs for drifted ones. It only touches agent-authored skills, never repository-built-in or hub-installed skills, and never auto-deletes anything -- the worst outcome is reversible archival (Curator documentation \[5] as of 2026-06). So "growth" has two tracks: the memory layer (facts about you and the work) and the skill layer (capabilities the agent accumulates and rotates on its own).

<Note>
  **Difference from OpenClaw's growth mechanism**

  Both rely on the agent actively writing memory files to grow. Hermes-Agent's memory files have character limits that enforce consolidation, and there is a complete SQLite + FTS5 history for searchable recall. The difference is in "strictness of curation" and "depth of recallability," not in the degree of automation.
</Note>

## 3. System structure

Hermes-Agent has four entry points, each serving a different context:

<Tree>
  <Tree.Folder name="hermes-agent/" defaultOpen>
    <Tree.File name="cli.py · CLI entry point (hermes command)" />

    <Tree.File name="run_agent.py · AIAgent main loop" />

    <Tree.File name="hermes_state.py · SQLite state database" />

    <Tree.File name="model_tools.py · tool discovery and dispatch" />

    <Tree.File name="toolsets.py · 28 toolset groups" />

    <Tree.Folder name="gateway/" defaultOpen>
      <Tree.File name="run.py · Gateway entry point (multi-channel persistent connection)" />
    </Tree.Folder>

    <Tree.Folder name="agent/" defaultOpen>
      <Tree.File name="prompt_builder.py · three-layer system prompt assembly" />

      <Tree.File name="system_prompt.py · slot definitions per layer" />

      <Tree.File name="memory_manager.py · memory management" />

      <Tree.File name="curator.py · background lifecycle maintenance for agent-authored skills" />
    </Tree.Folder>

    <Tree.Folder name="skills/" defaultOpen>
      <Tree.File name="SKILL.md · skill format definition" />
    </Tree.Folder>

    <Tree.Folder name="adapters/" defaultOpen>
      <Tree.File name="· third-party integrations (Telegram, Discord, ACP, etc.)" />
    </Tree.Folder>
  </Tree.Folder>
</Tree>

Core components and responsibilities:

| Component                     | Responsibility                                                                                              |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `AIAgent` (in `run_agent.py`) | Main loop: receive message, assemble prompt, call model, dispatch tools, write back state                   |
| `prompt_builder.py`           | Three-layer system prompt assembly (stable, context, volatile)                                              |
| `model_tools.py`              | Tool discovery, schema collection, dispatch by model capability (multimodal, local execution, external API) |
| `hermes_state.py`             | SQLite + FTS5 state database, session search, LLM-summary recall                                            |
| `memory_manager.py`           | Coordinate reads and writes between `MEMORY.md` and SQLite memory                                           |

Sub-agent delegation is triggered via the `delegate_task` tool: 3 concurrent sub-agents by default, each receiving an independent context and terminal session (official documentation \[2] as of 2026-05). Six terminal backends are available -- local, Docker, SSH, Singularity, Modal, Daytona -- providing flexibility from "run directly on local machine" to "isolated cloud environment."

<Note>
  **A typical agent turn, step by step**

  <Steps>
    <Step title="User sends a message on Telegram">
      The Telegram adapter receives the message and passes it to the Gateway.
    </Step>

    <Step title="Gateway finds the corresponding session">
      The message is passed to `AIAgent`.
    </Step>

    <Step title="AIAgent calls prompt_builder to assemble the three-layer prompt">
      Fetches `SOUL.md` (stable) + `AGENTS.md` (context, walking up from CWD to git root) + `MEMORY.md` + `USER.md` (volatile, from the SQLite frozen snapshot).
    </Step>

    <Step title="Assembled prompt is sent to the model">
      The model response may contain a tool\_call; `model_tools` dispatches based on model and tool configuration, possibly calling `delegate_task` to spawn sub-agents.
    </Step>

    <Step title="Sub-agents return results">
      The main agent consolidates and replies on Telegram; state is written back to SQLite.
    </Step>
  </Steps>

  Every link in this chain can be replaced or disabled. That is the real meaning of "minimalist shell": the core skeleton is not complex, but every junction is exposed as configurable.
</Note>

### User-side configuration directory: `~/.hermes/`

The `hermes-agent/` structure above is the source code layout (the developer's view). What you actually edit day-to-day is a different tree: the runtime configuration directory `HERMES_HOME`, defaulting to `~/.hermes/` on Linux, macOS, and WSL2 (`%LOCALAPPDATA%\hermes\` on Windows native). It supports profiles, with each isolated instance having its own `HERMES_HOME` (Configuration documentation \[6] as of 2026-06).

<Tree>
  <Tree.Folder name="~/.hermes/" defaultOpen>
    <Tree.File name="config.yaml · main configuration (non-secret): model, terminal, memory limits, compression, security" />

    <Tree.File name=".env · secrets: API keys, platform tokens, passwords" />

    <Tree.File name="auth.json · OAuth credentials" />

    <Tree.File name="SOUL.md · agent persona (system prompt stable slot #1)" />

    <Tree.Folder name="memories/" defaultOpen>
      <Tree.File name="MEMORY.md · cross-session memory, 2,200 character limit" />

      <Tree.File name="USER.md · user preferences, 1,375 character limit" />
    </Tree.Folder>

    <Tree.Folder name="skills/" defaultOpen>
      <Tree.File name="· agent-authored skills (lifecycle managed by curator)" />
    </Tree.Folder>

    <Tree.Folder name="cron/" defaultOpen>
      <Tree.File name="· scheduled tasks" />
    </Tree.Folder>

    <Tree.Folder name="sessions/" defaultOpen>
      <Tree.File name="· Gateway conversation state" />
    </Tree.Folder>

    <Tree.Folder name="logs/" defaultOpen>
      <Tree.File name="agent.log · errors.log · gateway.log" />
    </Tree.Folder>

    <Tree.File name="state.db · SQLite + FTS5 complete history" />
  </Tree.Folder>
</Tree>

The core split in configuration is two layers: `config.yaml` (non-secret, can go into version control) and `.env` (secret, never into version control). `config.yaml` references `.env` environment variables with `${VAR_NAME}` syntax -- API keys stay out of `config.yaml`. The repository ships a `cli-config.yaml.example` at the root with full inline comments as a starting template. Selected key fields (as of 2026-06):

```yaml theme={null}
model:
  provider: openrouter          # openrouter | anthropic | openai | gemini | nous | auto
  default: anthropic/claude-opus-4.6
  base_url: https://openrouter.ai/api/v1   # any OpenAI-compatible endpoint; local vLLM / llama.cpp goes here
  # api_key goes in ~/.hermes/.env, not here

memory:
  memory_char_limit: 2200       # MEMORY.md limit, corresponds to ~800 tokens in Section 2.5
  user_char_limit: 1375         # USER.md limit, corresponds to ~500 tokens

agent:
  max_concurrent_children: 3    # concurrent sub-agents
  max_spawn_depth: 1

terminal:
  backend: docker               # local | docker | ssh | modal | daytona | singularity

security:
  redact_secrets: true
  approvals:
    mode: manual                # manual | smart | off
```

| File                                     | Version control?                | Responsibility                                                             |
| ---------------------------------------- | ------------------------------- | -------------------------------------------------------------------------- |
| `config.yaml`                            | Yes (non-secret)                | Model, terminal backend, memory limits, compression, security, concurrency |
| `.env`                                   | No (secret)                     | API keys, Telegram/Discord/Slack platform tokens, passwords                |
| `auth.json`                              | No (secret)                     | OAuth credentials                                                          |
| `SOUL.md`                                | Yes                             | Agent persona, stable slot #1                                              |
| `memories/MEMORY.md`, `memories/USER.md` | Situational                     | Agent-curated long-term memory and user profile                            |
| `state.db`                               | No (contains raw conversations) | Full session text history; backup strategy must include this               |

<Warning>
  **`.env` and `state.db` must never enter a public repository**

  `config.yaml` is designed to go into version control as non-secret configuration. But `.env` (API keys, platform tokens) and `state.db` (containing all raw conversation content) become a credentials leak and privacy incident the moment they enter a public repo. Add `~/.hermes/.env`, `~/.hermes/state.db`, and `~/.hermes/auth.json` to `.gitignore`; commit only `config.yaml` and `cli-config.yaml.example`.
</Warning>

## 4. Custom Markdown files: the role of SOUL.md and others

Hermes-Agent's Markdown file list is leaner than OpenClaw's, but each file maps to a specific slot in the prompt assembly. The list and responsibilities below come from the three-layer assembly order in `agent/system_prompt.py` (Prompt Assembly documentation \[3] as of 2026-05):

| File                                     | Layer            | Responsibility                                                  | Location                     |
| ---------------------------------------- | ---------------- | --------------------------------------------------------------- | ---------------------------- |
| `SOUL.md`                                | stable (slot #1) | Agent persona and identity; fully replaces the default identity | `$HERMES_HOME/SOUL.md`       |
| `AGENTS.md` / `.hermes.md` / `CLAUDE.md` | context          | Project instructions and conventions; first matching file wins  | Walk up from CWD to git root |
| `MEMORY.md`                              | volatile         | Agent cross-session notes, 2,200 character limit                | `$HERMES_HOME/memories/`     |
| `USER.md`                                | volatile         | User preferences and communication style, 1,375 character limit | `$HERMES_HOME/memories/`     |

Several key design points:

* **`SOUL.md` is only loaded from `HERMES_HOME`**, independent of CWD. This ensures the persona stays stable across projects: the persona you wrote for project A cannot be overwritten by project B's `AGENTS.md`.
* **The default `SOUL.md` is auto-planted:** on first launch, `hermes_cli/default_soul.py` writes a short identity declaration. Read that default before editing it -- then you know "if I delete this entire block, what happens."
* **Volatile layer frozen snapshot:** at session start, the contents of `MEMORY.md` and `USER.md` are frozen into a snapshot and injected into the system prompt. Memory operations during the session **only write to disk; they take effect in the next session.** This design preserves LLM prefix cache stability (the prompt prefix does not change within a session), at the cost of "in-session self-improvement is not visible until the next round."
* **Sub-agent delegation exception:** when `delegate_task` launches a sub-agent (`skip_context_files`), `SOUL.md` is not loaded; the hardcoded `DEFAULT_AGENT_IDENTITY` is used instead. Sub-agents do not inherit the main persona -- this is the design choice that enables "different personas for different tasks."

<Warning>
  **The cost of volatile freezing**

  Changes you make to `MEMORY.md` mid-session **will not be read back by the current session.** If you expect "I say one thing, it remembers it, and uses it in the very next turn," you will be disappointed. Think of it as "writing in your diary each evening and reading it only when you wake up."
</Warning>

Comparison against [05-1 OpenClaw](/en/code-agent/case-studies/openclaw):

| Dimension                 | OpenClaw                                  | Hermes-Agent                                                                                                                                       |
| ------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Persona file              | `SOUL.md`                                 | `SOUL.md`                                                                                                                                          |
| Identity card             | `IDENTITY.md` (separate file)             | Merged into `SOUL.md`                                                                                                                              |
| Memory distillation layer | `MEMORY.md`                               | `MEMORY.md`                                                                                                                                        |
| Detailed log              | `memory/YYYY-MM-DD.md` (one file per day) | SQLite + FTS5                                                                                                                                      |
| User file                 | `USER.md`                                 | `USER.md`                                                                                                                                          |
| Tool conventions          | `TOOLS.md`                                | Merged into `AGENTS.md` or environment variables                                                                                                   |
| Project policy            | `AGENTS.md`                               | `AGENTS.md` / `.hermes.md` / `CLAUDE.md`                                                                                                           |
| Heartbeat                 | `HEARTBEAT.md` + Gateway scheduling       | Official documentation does not explicitly state whether Hermes-Agent natively supports heartbeat; check official documentation for current status |
| Bootstrap                 | `BOOTSTRAP.md`                            | Default `SOUL.md` auto-planted on first launch; no explicit ceremony                                                                               |

## 5. One everyday analogy

Hermes-Agent is like **an intern with a built-in "personal contract" and "morning briefing" mechanism:** before leaving home each day they pull out three documents from home (`$HERMES_HOME`) and read them -- "who I am" (`SOUL.md`), "what I learned yesterday" (`MEMORY.md`), and "what the boss prefers" (`USER.md`) -- then walk into the office and read the project manual (`AGENTS.md`, found by walking up from the nearest project folder). The three documents from home do not change during the day (frozen snapshot), and any notes they take during the day go into a desk drawer that can only be read the next morning. Occasionally they delegate work to three independent assistants (sub-agents) who do not inherit the intern's personal contract and only execute the specific tasks assigned. **The person is not fixed -- they are reassembled each morning; and the source for that reassembly (those three documents at home) is something you wrote for them, version-controllable.**

## 6. Trade-offs of minimalist design

### 6.1 What you gain

* **Human-readable, version-controllable, portable across platforms.** Everything about "who the agent is, what it should remember, how it should behave" is expressed in Markdown or diffable SQLite. No closed-source binary configuration.
* **Prefix-cache friendly.** The volatile frozen snapshot keeps the prompt prefix stable within a session, lowering model costs.
* **No vendor lock-in.** Any OpenAI-compatible API works -- a real strategic option in 2026's fast-shifting provider landscape.
* **Progressive skill disclosure.** Level 0 only loads the skills index (roughly 3k tokens); Level 1 loads full skill content. This is cost control for "many skills installed but not all used in every session."

### 6.2 What you pay

* **Plain-text format lacks structured schema validation.** Markdown files have no type checking; personality drift is hard to quantify. How do you know three rounds of edits to `SOUL.md` haven't pushed the tone away from the original design?
* **Volatile freezing makes in-session memory changes take effect with a delay.** This creates friction relative to the intuition of real-time awareness.
* **Persona differentiation in multi-agent scenarios:** sub-agents use hardcoded `DEFAULT_AGENT_IDENTITY`, but how do you inject different personas into different sub-agents? Official documentation does not describe a `SOUL.md` override mechanism for sub-agents.
* **SQLite is single-machine storage.** If you want to sync session history across multiple machines, you need to add a sync layer yourself (filesystem watcher, Syncthing, or a self-hosted S3-compatible layer).

<Tip>
  **When to choose Hermes-Agent over OpenClaw**

  * Your workflow is "one daemon serving the same me across multiple channels": choose Hermes-Agent -- the minimalist design fits exactly.
  * Your workflow is "one daemon running multiple independent-persona agents each serving different scenarios": choose OpenClaw -- its multi-channel routing and isolated session design fits better.
  * Your workflow is "personal local experimentation, no enterprise hardening needed": either works -- pick based on your Python vs. TypeScript preference.
  * Your workflow is "going to production / enterprise environment": neither is sufficient on its own; stack [05-3 NemoClaw](/en/code-agent/case-studies/nemoclaw)'s hardening shell on top.
</Tip>

***

## Hands-on exercises

<Steps>
  <Step title="Edit SOUL.md to verify the identity slot">
    Replace `~/.hermes/SOUL.md` with a custom persona (for example: "direct tone, conclusion first, refuses marketing speak, no emoji"), restart `hermes`, and observe the tone of the agent's opening in the new session. Remember: editing `SOUL.md` mid-session has no effect on the current session -- a restart is required.
  </Step>

  <Step title="Observe volatile freezing behavior">
    Use the `hermes memory` command to observe the add/remove log for `MEMORY.md`. Then ask the agent to remember something mid-session, and observe when it writes to disk (at session end) and when it reads back (at the start of the next session).
  </Step>

  <Step title="Read the prompt_builder.py source code">
    Clone the repository and read `agent/prompt_builder.py` and `agent/system_prompt.py` directly. Seeing "three-layer assembly" in code gives you better understanding than reading the documentation alone.
  </Step>
</Steps>

## Common pitfalls

* **Assuming Hermes-Agent only works with Hermes-series models:** any OpenAI-compatible API works (OpenRouter, Anthropic, OpenAI, Google, vLLM, llama.cpp, etc.)
* **Assuming editing `SOUL.md` takes effect immediately in the current session:** a restart is required because the volatile layer is frozen into a snapshot at session start
* **Treating `AGENTS.md` and `SOUL.md` as equivalent:** they belong to different prompt layers (context vs. stable) and have different scopes (CWD walk-up vs. unique `HERMES_HOME`)
* **Treating SQLite state as a disposable cache:** SQLite contains the basis for session search and LLM-summary recall; deleting it makes "why did it do that last time" unrecoverable. Backup strategy should include the SQLite file.
* **Expecting sub-agents to have the main persona:** sub-agents launched by `delegate_task` do not inherit `SOUL.md` and use `DEFAULT_AGENT_IDENTITY` directly. If a specific persona is needed for a sub-agent, pass the context explicitly at call time or set up a sub-agent-specific `SOUL.md` in advance; official documentation does not explicitly state whether native override support exists.

## Self-check

<Check>
  **The bar for passing this unit**

  1. Can you draw the three-layer system prompt (stable, context, volatile) with each slot and its corresponding file?
  2. Can you explain why the volatile layer uses a frozen snapshot rather than live injection, and what the implications are for memory consistency and inference cost?
  3. Can you state the design rationale for `SOUL.md` being loaded exclusively from `$HERMES_HOME` rather than following CWD?
  4. Can you identify the differences between Hermes-Agent and [05-1 OpenClaw](/en/code-agent/case-studies/openclaw) on three dimensions: persona file, memory storage, and heartbeat support?
  5. Is your workflow "one self serving multiple channels" or "multiple selves each serving different contexts"? Which framework does that choice point toward?
</Check>

## Sources and further reading

Factual claims are grounded in official documentation; fast-changing items are annotated as of 2026-05.

<div className="references">
  * \[1] Nous Research, "hermes-agent," GitHub repository. [https://github.com/NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent) (as of 2026-05)

  * \[2] Nous Research, "Hermes-Agent Documentation," official docs. [https://hermes-agent.nousresearch.com/docs/](https://hermes-agent.nousresearch.com/docs/) (as of 2026-05)

  * \[3] Nous Research, "Prompt Assembly," Hermes-Agent Developer Guide. [https://hermes-agent.nousresearch.com/docs/developer-guide/prompt-assembly](https://hermes-agent.nousresearch.com/docs/developer-guide/prompt-assembly) (as of 2026-05)

  * \[4] Nous Research, "Memory," Hermes-Agent User Guide. [https://hermes-agent.nousresearch.com/docs/user-guide/features/memory](https://hermes-agent.nousresearch.com/docs/user-guide/features/memory) (as of 2026-06)

  * \[5] Nous Research, "Curator," Hermes-Agent User Guide. [https://hermes-agent.nousresearch.com/docs/user-guide/features/curator](https://hermes-agent.nousresearch.com/docs/user-guide/features/curator) (as of 2026-06)

  * \[6] Nous Research, "Configuration," Hermes-Agent User Guide. [https://hermes-agent.nousresearch.com/docs/user-guide/configuration](https://hermes-agent.nousresearch.com/docs/user-guide/configuration) (as of 2026-06)

  * \[7] Nous Research, "Personality and SOUL.md," Hermes-Agent User Guide. [https://hermes-agent.nousresearch.com/docs/user-guide/features/personality](https://hermes-agent.nousresearch.com/docs/user-guide/features/personality) (as of 2026-05)

  * \[8] Nous Research, "Context Files," Hermes-Agent User Guide. [https://hermes-agent.nousresearch.com/docs/user-guide/features/context-files](https://hermes-agent.nousresearch.com/docs/user-guide/features/context-files) (as of 2026-05)

  * \[9] Nous Research, "cli-config.yaml.example," GitHub. [https://github.com/NousResearch/hermes-agent/blob/main/cli-config.yaml.example](https://github.com/NousResearch/hermes-agent/blob/main/cli-config.yaml.example) (as of 2026-06)
</div>

* Related: [05-1 OpenClaw](/en/code-agent/case-studies/openclaw) (TypeScript, more Markdown files, more complete heartbeat scheduling); [05-3 NemoClaw](/en/code-agent/case-studies/nemoclaw) (NVIDIA open-sourced 2026-03, wraps Hermes inside an OpenShell sandbox, enterprise-ready); [05-4 Three-approaches comparison](/en/code-agent/case-studies/three-approaches-comparison); [01-6 Harness Engineering](/en/code-agent/foundations/harness-engineering) for the harness design criteria this unit's architecture embodies.
