> ## 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 解剖：Nous Research 的極簡代理

> 從三層 prompt 組裝到 SQLite 記憶策展，解剖 Hermes-Agent 的架構決策與取捨代價，讓你在「一個我服務多管道」與「多個我各服務不同場景」之間做出有憑據的選擇。

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="zh"
  items={[
"Hermes-Agent 是框架不是模型，別把兩個 Hermes 混。",
"三層 prompt：stable 改最少，volatile 凍結快照。",
"session 內改 SOUL.md 對當前 session 無效，必須重啟。",
"子代理不繼承主人格，用的是硬編碼的 DEFAULT_AGENT_IDENTITY。",
"預設 SOUL.md 自動植入，改之前先讀 default_soul.py 那份。",
"SQLite 含 session 摘要依據，誤刪等於失憶。",
"凍結快照是省 prefix cache 成本；任何 OpenAI-compatible API 都能接。",
]}
/>

<Info>
  **這個單元解決什麼問題**

  Hermes-Agent 是 Nous Research 開源的 AI 代理框架，以「可自我成長的代理」為定位。它不綁定任何推論後端，也不強制使用 Hermes 模型家族，用一組 Markdown 檔定義代理的人格、記憶與指令，並透過三層 system prompt 組裝把這些檔案有紀律地注入。本單元解剖它的由來、核心技術、系統結構與 md 檔設計，讓你看懂「極簡外殼」背後的設計選擇與取捨代價。
</Info>

## 學習目標

* [ ] 能說清 Hermes-Agent 的由來與定位：它是代理框架，而非推論模型，由 Nous Research 維護，MIT 授權
* [ ] 能描述其核心架構：`AIAgent` 主迴圈、三層 system prompt 組裝、多後端工具分派、SQLite 狀態儲存
* [ ] 能說出 `SOUL.md`、`MEMORY.md`、`USER.md`、`AGENTS.md` 各自在 prompt 組裝中的層次與職責
* [ ] 能指出極簡 md 檔設計的取捨代價：易於理解與外部化，卻在高並發或多代理場景下缺乏結構化版本管控
* [ ] 能用一句生活比喻概括這個系統的本質
* [ ] 能與 [05-1 OpenClaw](/code-agent/case-studies/openclaw) 的多 md 檔設計做正面對位

***

## 1. 由來與定位

Hermes-Agent 由 Nous Research 開源維護，定位為「The agent that grows with you」（官方倉庫 \[1] 截至 2026-05）。它是 Nous Research 獨立開源的全新框架，不衍生自 OpenClaw；提供的 `hermes claw migrate` 指令是給既有 OpenClaw 使用者遷移設定、記憶與 skill 的轉換工具，兩者為不同 org 的獨立專案（官方文件 \[2] 截至 2026-05）。

幾個常見誤會要先釐清：

* **它不是 Hermes 模型家族。** Hermes-3-405B、Hermes-3-70B 等是 Nous Research 訓練的開源 LLM，與 Hermes-Agent 框架是兩個獨立產品。Hermes-Agent 不綁定任何推論後端，支援 OpenRouter、Anthropic、OpenAI、Google、vLLM、llama.cpp 等任何 OpenAI-compatible API；Hermes 模型只是 Nous Portal 的預設首選入口
* **它不是輕量 SDK。** 雖然 md 檔介面簡潔，但它定位為「可在 \$5 VPS 或 GPU 叢集執行、透過 Telegram/Discord 等多平台互動」的全功能 agentic harness，內建子代理委派、SQLite 狀態儲存、跨 session 記憶
* **授權為 MIT**（截至 2026-05 倉庫 LICENSE 檔），可商用、可改、可閉源重發

<Note>
  **命名澄清**

  「Hermes」在 Nous Research 的命名體系裡同時指三件事：Hermes 模型家族（LLM）、Hermes-Agent（代理框架）、Nous Portal（推論訂閱服務）。本單元談的是第二個，必要時會用全名「Hermes-Agent」以免混淆。
</Note>

## 2. 核心技術

Hermes-Agent 的技術堆疊可拆成五個軸：

| 軸        | 實作                                                          | 來源 / 備註                                                        |
| -------- | ----------------------------------------------------------- | -------------------------------------------------------------- |
| 主迴圈      | `run_agent.py` 的 `AIAgent` 類別                               | 官方文件以「主迴圈」描述；確切 API 與方法簽名以官方文件為準                               |
| API 模式   | `chat_completion` / `codex_responses` / `anthropic`(native) | 依推論後端自動選取                                                      |
| 工具系統     | 70+ 工具、28 個 toolset 分組                                      | 集中在 `model_tools.py` 與 `toolsets.py`                           |
| 狀態持久化    | SQLite + FTS5                                               | `hermes_state.py`，支援跨 session 搜尋與 LLM 摘要回溯                     |
| Skill 載入 | `skills/` 目錄、`SKILL.md` 格式                                  | 漸進揭露（progressive disclosure）：Level 0 只載入 index，Level 1 才載入完整內容 |

相對於 OpenClaw 的「多 md 檔 + 多管道路由」設計，Hermes-Agent 的極簡性表現在三個地方：

1. **prompt 組裝是三層而非散落各處。** `agent/prompt_builder.py` 負責把 stable / context / volatile 三層拼成 system prompt，每層有明確的 slot 編號與注入策略
2. **推論後端完全可插拔。** 任何能接受 OpenAI 相容 API 的服務都能接，這是給「不依賴單一供應商」的設計選擇
3. **狀態是 SQLite 而非純檔案。** OpenClaw 的記憶主要靠 `MEMORY.md` + `memory/YYYY-MM-DD.md`；Hermes-Agent 多了一層 SQLite 與 FTS5，支援跨 session 全文搜尋與 LLM 摘要回溯

<Tip>
  **三層 prompt 組裝的設計理由**

  把 prompt 拆層而非寫成一大段，是因為不同層的「變動頻率」與「快取命中需求」不同：stable 層（人格）幾乎不變，prefix cache 命中率最高；context 層（專案指令）隨 CWD 變動；volatile 層（使用者與記憶）每 session 重抓。分層讓你只重抓有變動的部分，省推論成本。Hermes-Agent 進一步把 volatile 層「凍結快照」，session 中的記憶操作只寫磁碟，下次 session 才生效（Prompt Assembly 文件 \[3] 截至 2026-05）。這個設計的權衡見[第 4 節](#4-自訂-md-檔soul-md-等的角色)。
</Tip>

<FrameworkMap
  lang="zh"
  title="Hermes-Agent 三層 system prompt 組裝"
  layers={[
{
  id: "stable",
  color: "teal",
  label: "stable 層 — 人格（幾乎不變）",
  modules: [
    {
      id: "soul",
      name: "SOUL.md",
      path: "$HERMES_HOME/SOUL.md",
      summary: "代理人格與身份，prefix cache 命中率最高",
      detail: "從 $HERMES_HOME 載入，不隨 CWD 改變，確保人格跨專案穩定。首次啟動由 default_soul.py 自動植入預設版本。幾乎不動，是 prefix cache 命中率最高的層。",
      relations: [{ to: "AGENTS.md", label: "人格 vs 政策" }, { to: "MEMORY.md", label: "人格 vs 學習" }],
    },
  ],
},
{
  id: "context",
  color: "blue",
  label: "context 層 — 專案指令（隨 CWD 變動）",
  modules: [
    {
      id: "agents",
      name: "AGENTS.md",
      path: "CWD → git root（首個符合的檔案優先）",
      summary: "專案指令與慣例，也接受 .hermes.md / CLAUDE.md",
      detail: "從 CWD 往上走到 git root，首個符合的檔案優先。也接受 .hermes.md 與 CLAUDE.md，讓不同專案共用同一個 Hermes daemon 而有各自的操作指令。",
      relations: [{ to: "SOUL.md", label: "專案覆寫人格外的操作" }],
    },
  ],
},
{
  id: "volatile",
  color: "purple",
  label: "volatile 層 — 記憶快照（session 起始凍結）",
  modules: [
    {
      id: "memory",
      name: "MEMORY.md",
      path: "$HERMES_HOME/memories/MEMORY.md",
      summary: "跨 session 記憶，上限 2,200 字元（約 800 token）",
      detail: "代理主動策展的長期記憶：環境事實、慣例、學到的東西。session 起始時凍結為快照注入；session 中的記憶操作僅寫磁碟，下次 session 才生效（volatile 凍結）。字元上限強迫 consolidation。",
      relations: [{ to: "USER.md", label: "記憶 vs 使用者 profile" }, { to: "state.db", label: "精選筆記 vs 完整歷史" }],
    },
    {
      id: "user",
      name: "USER.md",
      path: "$HERMES_HOME/memories/USER.md",
      summary: "使用者偏好與溝通風格，上限 1,375 字元（約 500 token）",
      detail: "記錄使用者偏好、溝通風格與期待。同樣在 session 起始時凍結快照。與 MEMORY.md 的分工：MEMORY.md 記「工作事實」，USER.md 記「你是誰、你怎麼溝通」。",
      relations: [{ to: "MEMORY.md", label: "使用者 vs 工作" }],
    },
  ],
},
{
  id: "infra",
  color: "amber",
  label: "基礎設施層 — 狀態與工具",
  modules: [
    {
      id: "statedb",
      name: "state.db",
      path: "$HERMES_HOME/state.db",
      summary: "SQLite + FTS5 完整 session 歷史",
      detail: "所有 CLI 與訊息 session 的完整歷史，支援 session_search 全文搜尋與 LLM 摘要回溯。與 MEMORY.md 是兩套獨立系統：state.db 是可搜尋日誌，MEMORY.md 是 agent 精選的長期筆記，前者不自動蒸餾成後者。",
      relations: [{ to: "MEMORY.md", label: "詳盡日誌 vs 精選" }],
    },
    {
      id: "curator",
      name: "curator.py",
      path: "agent/curator.py",
      summary: "agent 自建 skill 的背景 lifecycle 維護",
      detail: "追蹤 agent 自建 skill 的使用頻率；30 天未用轉 stale、90 天封存（永不自動刪除）。週期性派輔助模型 review，提議整併重疊或修補漂移。只碰 agent 自建的 skill，不動倉庫內建或 hub 安裝的 skill。",
      relations: [{ to: "skills/", label: "管理 lifecycle" }],
    },
    {
      id: "prompt-builder",
      name: "prompt_builder.py",
      path: "agent/prompt_builder.py",
      summary: "三層 system prompt 組裝引擎",
      detail: "負責把 stable / context / volatile 三層按 slot 編號拼成完整 system prompt，送進任何 OpenAI-compatible API。同一 session 內 prompt 前綴不變，prefix cache 穩定。",
      relations: [{ to: "SOUL.md", label: "stable slot #1" }, { to: "AGENTS.md", label: "context slot" }, { to: "MEMORY.md", label: "volatile slot" }],
    },
  ],
},
]}
/>

## 2.5 為什麼能「自我成長」：代理自己策展記憶，下個 session 回注

「grows with you」的機制不是背景自動摘要，而是 **agent 主動策展自己的記憶**（Memory 文件 \[4] 截至 2026-06）。拆開看：

1. **兩份有上限的記憶檔**：`MEMORY.md`（上限 2,200 字元，約 800 token）記「環境事實、慣例、學到的東西」，`USER.md`（上限 1,375 字元，約 500 token）記「你的偏好、溝通風格、期待」，都放在 `~/.hermes/memories/`。
2. **由 agent 透過 `memory` 工具自己增刪改**：不是有個背景程序自動蒸餾，而是 agent 在互動中判斷「這條值得記」，主動 add / replace / remove 寫進上面兩份檔。字元上限是關鍵設計：它強迫 agent 篩選與合併（consolidation），而非無限堆積，所以成長是被策展（curated）的濃縮，不是流水帳。
3. **下個 session 回注**：session 起始時，這兩份檔被凍結成快照、注入 volatile 層（見上面三層組裝）。agent 於是帶著「上次記下的你與這份工作」開場，行為更貼合。

另有一層 **SQLite + FTS5 完整歷史**（`~/.hermes/state.db`），存所有 CLI 與訊息 session，可用 `session_search` 工具撈回過去的原始訊息。注意它和上面的「主動記憶」是**兩套獨立系統**：SQLite 是可搜尋的詳盡日誌，`MEMORY.md` 與 `USER.md` 是 agent 精選的長期筆記；前者不會自動蒸餾成後者。

關鍵推論：成長發生在 **session 之間**，不在 session 之內，這正是 volatile 凍結的直接後果。一個 session 就是「讀上次的記憶 → 做事 → 把值得記的寫進磁碟」，下次再讀。這也說明了為什麼 `SOUL.md`（人格）固定不變、而 `MEMORY.md` 與 `USER.md`（學習）會長：人格是你預先定義的常數，成長只發生在記憶層。

記憶只是成長的一臂。另一臂是 **skill 自我演化**：agent 能自己寫 skill，`curator.py` 在背景維護這些「agent 自建 skill」的生命週期，追蹤使用頻率，把久未用的 skill 從 active 轉 stale（預設 30 天）、再轉 archived（90 天封存），並週期性派一個輔助模型 review，提議整併重疊或修補漂移。它只碰 agent 自建的 skill，不動倉庫內建或 hub 安裝的 skill，且永不自動刪除，最壞只到可復原的封存（Curator 文件 \[5] 截至 2026-06）。所以「成長」有兩條線：記憶層（你與工作的事實）與技能層（agent 自己累積、自己汰換的能力）。

<Note>
  **與 OpenClaw 的成長機制差異**

  兩者都靠 agent 主動寫記憶檔來成長；Hermes-Agent 的記憶檔有字元上限、強制 consolidation，另有 SQLite + FTS5 的完整歷史可搜尋回溯。差別在「精選的嚴格度」與「可回溯的詳盡度」，不在自動化程度。
</Note>

## 3. 系統結構

Hermes-Agent 的入口點分四個，各自服務不同情境：

<Tree>
  <Tree.Folder name="hermes-agent/" defaultOpen>
    <Tree.File name="cli.py · CLI 入口（hermes 指令）" />

    <Tree.File name="run_agent.py · AIAgent 主迴圈" />

    <Tree.File name="hermes_state.py · SQLite 狀態資料庫" />

    <Tree.File name="model_tools.py · 工具探索與分派" />

    <Tree.File name="toolsets.py · 28 個 toolset 分組" />

    <Tree.Folder name="gateway/" defaultOpen>
      <Tree.File name="run.py · Gateway 入口（多管道長連）" />
    </Tree.Folder>

    <Tree.Folder name="agent/" defaultOpen>
      <Tree.File name="prompt_builder.py · 三層 system prompt 組裝" />

      <Tree.File name="system_prompt.py · 各層 slot 定義" />

      <Tree.File name="memory_manager.py · 記憶管理" />

      <Tree.File name="curator.py · agent 自建 skill 的背景 lifecycle 維護" />
    </Tree.Folder>

    <Tree.Folder name="skills/" defaultOpen>
      <Tree.File name="SKILL.md · 技能格式定義" />
    </Tree.Folder>

    <Tree.Folder name="adapters/" defaultOpen>
      <Tree.File name="· 第三方整合（Telegram、Discord、ACP 等）" />
    </Tree.Folder>
  </Tree.Folder>
</Tree>

核心元件與職責：

| 元件                          | 職責                                               |
| --------------------------- | ------------------------------------------------ |
| `AIAgent`（在 `run_agent.py`） | 主迴圈：接收訊息 → 組裝 prompt → 呼叫模型 → 分派工具 → 寫回狀態        |
| `prompt_builder.py`         | 三層 system prompt 組裝（stable → context → volatile） |
| `model_tools.py`            | 工具探索、schema 收集、依模型能力分派（多模態、本地執行、外部 API）          |
| `hermes_state.py`           | SQLite + FTS5 狀態資料庫，session search、LLM 摘要回溯      |
| `memory_manager.py`         | 協調 `MEMORY.md` 與 SQLite 記憶的讀寫                    |

子代理委派透過 `delegate_task` 工具觸發：預設 3 個並發子代理，每個子代理獲得獨立 context 與 terminal session（官方文件 \[2] 截至 2026-05）。terminal 後端有六種：local、Docker、SSH、Singularity、Modal、Daytona，提供從「本機直接執行」到「雲端隔離環境」的彈性。

<Note>
  **一次典型的 agent turn 內部流程**

  <Steps>
    <Step title="使用者在 Telegram 傳訊息">
      Telegram adapter 收到訊息，丟進 Gateway。
    </Step>

    <Step title="Gateway 找對應 session">
      把訊息丟給 `AIAgent`。
    </Step>

    <Step title="AIAgent 呼叫 prompt_builder 組裝三層 prompt">
      抓 `SOUL.md`（stable）+ `AGENTS.md`（context，CWD 往上走到 git root）+ `MEMORY.md` + `USER.md`（volatile，從 SQLite 凍結快照）。
    </Step>

    <Step title="拼好後送進模型">
      模型回應可能含 tool\_call；`model_tools` 依模型與工具設定分派，可能呼叫 `delegate_task` 起子代理。
    </Step>

    <Step title="子代理回傳結果">
      主代理整理後回 Telegram；狀態寫回 SQLite。
    </Step>
  </Steps>

  這條鏈裡的每個環節都是可被替換或關閉的，這是「極簡外殼」的真意：核心骨架不複雜，但每個接點都暴露成可設定。
</Note>

### 使用者側設定目錄：`~/.hermes/`

上面的 `hermes-agent/` 是原始碼結構（開發者視角）。日常你實際編輯的是另一棵樹：執行期的設定目錄 `HERMES_HOME`，預設 `~/.hermes/`（Linux、macOS、WSL2；Windows native 為 `%LOCALAPPDATA%\hermes\`）。它支援 profiles，每個隔離實例有自己的 `HERMES_HOME`（Configuration 文件 \[6] 截至 2026-06）。

<Tree>
  <Tree.Folder name="~/.hermes/" defaultOpen>
    <Tree.File name="config.yaml · 主設定（非機密）：模型、terminal、記憶上限、壓縮、安全" />

    <Tree.File name=".env · 機密：API key、各平台 token、password" />

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

    <Tree.File name="SOUL.md · 代理人格（system prompt stable slot #1）" />

    <Tree.Folder name="memories/" defaultOpen>
      <Tree.File name="MEMORY.md · 跨 session 記憶，上限 2,200 字元" />

      <Tree.File name="USER.md · 使用者偏好，上限 1,375 字元" />
    </Tree.Folder>

    <Tree.Folder name="skills/" defaultOpen>
      <Tree.File name="· 代理自建 skill（curator 維護生命週期）" />
    </Tree.Folder>

    <Tree.Folder name="cron/" defaultOpen>
      <Tree.File name="· 排程任務" />
    </Tree.Folder>

    <Tree.Folder name="sessions/" defaultOpen>
      <Tree.File name="· Gateway 對話狀態" />
    </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 完整歷史" />
  </Tree.Folder>
</Tree>

設定的核心切分是兩層：`config.yaml`（非機密、可進版控）與 `.env`（機密、永不進版控）。`config.yaml` 用 `${VAR_NAME}` 語法引用 `.env` 的環境變數，API key 不寫進 `config.yaml`。倉庫根目錄附一份 `cli-config.yaml.example`（含完整行內說明）當起手範本。精選關鍵欄位（截至 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   # 任何 OpenAI-compatible 端點，本地 vLLM / llama.cpp 填這裡
  # api_key 放 ~/.hermes/.env，不寫這裡

memory:
  memory_char_limit: 2200       # MEMORY.md 上限，對應第 2.5 節的 ~800 token
  user_char_limit: 1375         # USER.md 上限，對應 ~500 token

agent:
  max_concurrent_children: 3    # 並發子代理數
  max_spawn_depth: 1

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

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

| 檔案                                      | 進版控？     | 職責                                                |
| --------------------------------------- | -------- | ------------------------------------------------- |
| `config.yaml`                           | 可（非機密）   | 模型、terminal 後端、記憶上限、壓縮、安全、並發                      |
| `.env`                                  | 否（機密）    | API key、Telegram、Discord、Slack 等平台 token、password |
| `auth.json`                             | 否（機密）    | OAuth 憑證                                          |
| `SOUL.md`                               | 可        | 代理人格，stable slot #1                               |
| `memories/MEMORY.md`、`memories/USER.md` | 視情況      | 代理策展的長期記憶與使用者 profile                             |
| `state.db`                              | 否（含原始對話） | session 全文歷史，備份策略要納入                              |

<Warning>
  **`.env` 與 `state.db` 永不進公開版控**

  `config.yaml` 設計上就是要進版控的非機密設定；但 `.env`（API key、平台 token）與 `state.db`（含所有原始對話內容）一旦進公開 repo 就是憑證外洩與隱私事故。把 `~/.hermes/.env`、`~/.hermes/state.db`、`~/.hermes/auth.json` 加進 `.gitignore`，只 commit `config.yaml` 與 `cli-config.yaml.example`。
</Warning>

## 4. 自訂 md 檔：SOUL.md 等的角色

Hermes-Agent 的 md 檔清單比 OpenClaw 精簡，但每個都對應 prompt 組裝的特定 slot。以下清單與職責來自 `agent/system_prompt.py` 的三層組裝順序（Prompt Assembly 文件 \[3] 截至 2026-05）：

| 檔案                                       | 層次               | 職責                         | 位置                       |
| ---------------------------------------- | ---------------- | -------------------------- | ------------------------ |
| `SOUL.md`                                | stable (slot #1) | 代理人格與身份，完整替換預設 identity    | `$HERMES_HOME/SOUL.md`   |
| `AGENTS.md` / `.hermes.md` / `CLAUDE.md` | context          | 專案指令與慣例，首個符合的檔案優先          | CWD 往上走到 git root        |
| `MEMORY.md`                              | volatile         | 代理跨 session 筆記，上限 2,200 字元 | `$HERMES_HOME/memories/` |
| `USER.md`                                | volatile         | 使用者偏好與溝通風格，上限 1,375 字元     | `$HERMES_HOME/memories/` |

幾個關鍵設計點：

* **`SOUL.md` 只從 `HERMES_HOME` 載入**，不隨 CWD 改變。這個設計確保人格跨專案穩定：你在 A 專案寫的人格，不會被 B 專案的 `AGENTS.md` 覆寫
* **預設 `SOUL.md` 自動植入**：首次啟動時由 `hermes_cli/default_soul.py` 寫入，內容為簡短的身份宣告。改之前先讀這份預設，你就知道「如果我把這整段刪掉，會發生什麼」
* **volatile 層凍結快照**：session 起始時 `MEMORY.md` 與 `USER.md` 的內容被凍結成 snapshot 注入 system prompt，session 中的記憶操作**僅寫入磁碟，下次 session 才生效**。這個設計保護 LLM prefix cache 穩定性（同一個 session 內的 prompt 前綴不變），代價是「session 內的自我改進要等下一輪才看得到」
* **子代理委派時的例外**：呼叫 `delegate_task` 啟動子代理時（`skip_context_files`），`SOUL.md` 不載入，改用硬編碼的 `DEFAULT_AGENT_IDENTITY`。子代理不繼承主人格，這是給「不同任務用不同人格」的彈性

<Warning>
  **volatile 凍結的代價**

  你在 session 中間對 `MEMORY.md` 的修改，**這個 session 內不會被這個 session 自己讀到**。如果你預期「我說一句、它記一句、下一句就用到」，會失望。把它想成「每天晚上寫日記、明早起床才看得到」。
</Warning>

與 [05-1 OpenClaw](/code-agent/case-studies/openclaw) 的對位：

| 維度        | OpenClaw                     | Hermes-Agent                             |
| --------- | ---------------------------- | ---------------------------------------- |
| 人格檔       | `SOUL.md`                    | `SOUL.md`                                |
| 身份卡       | `IDENTITY.md`（額外一份）          | 併入 `SOUL.md`                             |
| 記憶精煉層     | `MEMORY.md`                  | `MEMORY.md`                              |
| 詳細日誌      | `memory/YYYY-MM-DD.md`（每日一檔） | SQLite + FTS5                            |
| 使用者檔      | `USER.md`                    | `USER.md`                                |
| 工具慣例      | `TOOLS.md`                   | 併入 `AGENTS.md` 或環境變數                     |
| 專案政策      | `AGENTS.md`                  | `AGENTS.md` / `.hermes.md` / `CLAUDE.md` |
| 心跳        | `HEARTBEAT.md` + Gateway 排程  | 官方文件未明確說明是否原生支援 heartbeat；以官方文件為準        |
| bootstrap | `BOOTSTRAP.md`               | 首次啟動自動植入預設 `SOUL.md`，無顯式儀式               |

## 5. 一句生活比喻

Hermes-Agent 像一個**有內建「自我契約」與「晨間簡報」機制的實習生**：他出門上班前會把三份文件從家裡（`$HERMES_HOME`）抓出來讀，分別是「我是誰」（`SOUL.md`）、「昨天我學到什麼」（`MEMORY.md`）、「老闆喜歡什麼」（`USER.md`），進到辦公室再讀一份專案手冊（`AGENTS.md`，從最近的專案資料夾往上找）。這三份家裡的文件今天之內不會再改（凍結快照），他寫筆記也只能寫進抽屜、明早才讀得到；而他偶爾會把工作分派給三個彼此獨立的助理（子代理），這些助理不繼承他的內建契約，只執行你指派的特定任務。**人不是固定的，是每天早上重組一次；而重組的來源（家裡那三份文件）是你替他寫好的、可版控的。**

## 6. 極簡設計的取捨

### 6.1 你得到的好處

* **人可讀、可版控、可跨平台移植。** 所有「代理是誰、要記什麼、要怎麼做」都用 md 檔或可 diff 的 SQLite 表達，沒有閉源的二進位設定
* **prefix cache 友好。** volatile 凍結快照讓同一 session 內的 prompt 前綴穩定，模型成本下降
* **不綁供應商。** 任何 OpenAI-compatible API 都能接，這在 2026 年的供應商快速變動環境裡是實質的戰略選項
* **Skill 漸進揭露。** Level 0 只載入 skills index（約 3k tokens），Level 1 才載入完整 skill 內容，是給「裝很多 skill 但不是每個 session 都用」的成本控制

### 6.2 你付出的代價

* **純文字格式缺乏結構化 schema 驗證。** md 檔沒有型別檢查，personality drift（人格漂移）難以量化：你怎麼知道 `SOUL.md` 改了三輪後沒有把語氣推離原本的設計？
* **volatile 凍結使 session 中記憶延遲生效。** 與即時感知不符，UX 上有摩擦
* **多代理場景的人格差異化**：子代理用硬編碼的 `DEFAULT_AGENT_IDENTITY`，但如何為不同子代理注入不同人格？官方文件未說明是否提供子代理的 `SOUL.md` override 機制。
* **SQLite 是單機儲存。** 如果你想在多台機器之間同步 session 歷史，要自己接 sync layer（filesystem watcher、Syncthing、或自架 S3 相容層）

<Tip>
  **什麼時候選 Hermes-Agent 而不是 OpenClaw**

  * 你的工作流是「一個 daemon 服務多個管道的同一個我」：選 Hermes-Agent，極簡設計剛好
  * 你的工作流是「同一個 daemon 跑多個獨立人格的 agent 各自服務不同場景」：選 OpenClaw，多管道路由 + 隔離 session 設計更貼合
  * 你的工作流是「個人本地實驗、不需要企業硬化」：兩者皆可，看你偏好 Python 或 TypeScript
  * 你的工作流是「要上 production / 企業環境」：兩個都不夠，要疊 [05-3 NemoClaw](/code-agent/case-studies/nemoclaw) 的硬化外殼
</Tip>

***

## 動手做

<Steps>
  <Step title="改 SOUL.md 驗證 identity slot">
    把 `~/.hermes/SOUL.md` 換成自訂人格（例如「語氣直白、結論先行、拒絕行銷腔、不用 emoji」），重啟 `hermes` 觀察新 session 開頭 agent 的語氣變化。記得：session 內改 `SOUL.md` 對當前 session 無效，必須重啟。
  </Step>

  <Step title="觀察 volatile 凍結行為">
    用 `hermes memory` 指令觀察 `MEMORY.md` 的增刪記錄；然後在 session 內要求 agent 記下某件事，觀察它寫入磁碟的時機（session 結束時）與下次 session 開始讀回來的時機（session 啟動時）。
  </Step>

  <Step title="讀 prompt_builder.py 的原始碼">
    直接 clone 倉庫讀 `agent/prompt_builder.py` 與 `agent/system_prompt.py`，體會「三層組裝」在程式碼裡的形狀。這比讀文件更能掌握實作。
  </Step>
</Steps>

## 常見誤區

* **誤以為 Hermes-Agent 只能搭配 Hermes 系列模型**：實際上任何 OpenAI-compatible API 皆可（OpenRouter、Anthropic、OpenAI、Google、vLLM、llama.cpp 等）
* **誤以為修改 `SOUL.md` 當前 session 立即生效**：實際上需重啟代理，因為 volatile 層在 session 起始時凍結快照
* **誤以為 `AGENTS.md` 與 `SOUL.md` 功能相同**：兩者分屬不同 prompt 層次（context vs stable）與生效範圍（CWD 往上找 vs `HERMES_HOME` 唯一）
* **把 SQLite 狀態當成可丟棄的快取**：SQLite 含 session search 與 LLM 摘要回溯的依據，誤刪會讓「上次對話為什麼這樣做」變成不可考；備份策略應包含 SQLite 檔
* **子代理期待主人格**：`delegate_task` 啟動的子代理不繼承 `SOUL.md`，直接使用 `DEFAULT_AGENT_IDENTITY`。如果你需要子代理有特定人格，要自己在呼叫時傳入 context 或事先建立子代理專屬的 `SOUL.md`；官方文件未明確說明是否有原生 override 支援。

## 自我檢核

<Check>
  **通過本單元的標準**

  1. 能畫出 stable → context → volatile 三層 system prompt 的各個 slot 及其對應檔案嗎？
  2. 能解釋為什麼 volatile 層採用凍結快照而非即時注入，以及這對記憶一致性與推論成本各有什麼影響？
  3. 能說出 `SOUL.md` 為何固定從 `$HERMES_HOME` 載入、不隨 CWD 改變的設計理由嗎？
  4. 能指出 Hermes-Agent 相對於 [05-1 OpenClaw](/code-agent/case-studies/openclaw) 在「人格檔、記憶儲存、心跳支援」三個維度上的差異嗎？
  5. 你的工作流是「一個我服務多管道」還是「多個我各服務不同場景」？這個選擇會導向哪個框架？
</Check>

## 來源與延伸閱讀

事實主張依官方文件，快變動項標註截至 2026-05。

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

  * \[2] Nous Research, "Hermes-Agent Documentation," official docs. [https://hermes-agent.nousresearch.com/docs/](https://hermes-agent.nousresearch.com/docs/) （截至 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) （截至 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) （截至 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) （截至 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) （截至 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) （截至 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) （截至 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) （截至 2026-06）
</div>

* 銜接：[05-1 OpenClaw](/code-agent/case-studies/openclaw)（TypeScript、md 檔更多、心跳排程更完善）；[05-3 NemoClaw](/code-agent/case-studies/nemoclaw)（NVIDIA 2026-03 開源，把 Hermes 包進 OpenShell 沙箱，企業就緒）；[05-4 三條路線差異](/code-agent/case-studies/three-approaches-comparison)；[01-6 Harness Engineering](/code-agent/foundations/harness-engineering) 本單元架構所體現的 harness 設計判準。
