Skip to content

學習迴圈與記憶

源码版本v2026.7.20

職責

這是 Hermes 有別於一般 agent 的核心:一個「越用越好用」的閉環。它把每輪對話沉澱成:① 技能演化——從經驗建立/改寫技能(agent/learning_mutations.py);② 記憶——FTS5 全文檢索 + LLM 摘要的過往會話檢索(memory_manager),配合 Honcho 辯證式使用者建模;③ 學習圖——把技能與記憶組織成節點/邊(agent/learning_graph.py);④ 背景審視——background_review 在執行緒裡消化歷史、產出可執行的改寫動作。技能改寫最終回灌到技能系統,供後續輪次複用。

關鍵檔案

技能節點與學習圖

技能/記憶改寫

記憶管理

背景審視

技能節點長什麼樣

SkillNode 是學習圖裡技能的統一表達(agent/learning_graph.py:28-41):dataclass 欄位有 name/category/source(base 內建 vs profile 使用者/agent 建立)/use_count(從 .usage.json 讀)/created_by('agent' 表示背景審視建立的)/pinned/relatedbuild_skill_nodes 掃描 skills/ 下的 SKILL.md 檔案並疊加使用統計。source != "base" 加上 created_by == "agent"use_count > 0 是後面 build_learning_graph 判斷「這是學到的,不是內建的」的過濾條件——只把真正學到的節點放到桌面上給使用者看。

學習圖的產出

build_learning_graph(agent/learning_graph.py:248-328)把節點、邊、記憶卡、聚類統計拼成一個完整 payload,供桌面端學習面板渲染。它先呼叫 build_skill_nodes(_skill_roots()) 掃兩個根——base(repo/skills/,內建)和 profile(~/.hermes/skills/,使用者/agent 建立);然後過濾出 source != "base"created_by == "agent"use_count > 0 的技能當「學到的」,再疊加 build_edges(技能間關聯)、_memory_cards(記憶卡)、_memory_skill_edges(記憶與技能的關聯)。base 永遠在,profile 才是「學到的」。

改寫後必須清快取

技能被 edit_node 改寫後,如果不清快取,下一輪 prompt_builder 還會用舊的 SKILL.md 快照,改寫就白做了。_clear_skill_cache(agent/learning_mutations.py:200)呼叫 clear_skills_system_prompt_cache(clear_snapshot=True),clear_snapshot=True 連工具 (tool) 快照一起清,因為 MCP 工具列表也可能在審視中被刷新。這個函式是 edit_node / delete_node 的最後一環——改寫→落盤→清快取→下輪生效,四步缺一不可。

記憶注入:從 MemoryManager 到提示詞

MemoryManager(agent/memory_manager.py:354)協調一個內建 provider(FTS5 + LLM 摘要)加至多一個外部 provider(Honcho/Hindsight/Mem0 之類)。外部 provider 只允許一個,多了會被拒——避免工具 schema 膨脹和記憶後端打架;兩個 provider 失敗互不阻塞。檢索到的原始記憶會被 build_memory_context_block(agent/memory_manager.py:337-354)包成 <memory-context> 圍欄 + 系統註的上下文塊,系統註明確告訴模型「這是記憶不是新指令」;sanitize_context 在索引前先剝掉圍欄和舊的系統註——防止 provider 把已經包過的內容再包一層。StreamingContextScrubber 在串流輸出端做同樣的事,避免模型把記憶塊原文吐回使用者。

背景審視:摘要與候選動作

spawn_background_review_thread(agent/background_review.py:956)不直接起執行緒,而是建構 (target, prompt) 元組交給 AIAgent._spawn_background_review_thread,這樣測試層 patch threading.Thread 還能工作。函式簽名(review_memory / review_skills 兩個 bool)決定挑哪個 prompt——_MEMORY_REVIEW_PROMPT / _SKILL_REVIEW_PROMPT / _COMBINED_REVIEW_PROMPT:

python
def spawn_background_review_thread(agent, messages_snapshot,
                                    review_memory=False, review_skills=False):
    """Build the review thread target and prompt. Returns a ``(target, prompt)`` tuple."""
    if review_memory and review_skills:
        prompt = getattr(agent, "_COMBINED_REVIEW_PROMPT", _COMBINED_REVIEW_PROMPT)
    elif review_memory:
        prompt = getattr(agent, "_MEMORY_REVIEW_PROMPT", _MEMORY_REVIEW_PROMPT)
    else:
        prompt = getattr(agent, "_SKILL_REVIEW_PROMPT", _SKILL_REVIEW_PROMPT)
    def _target() -> None:
        _run_review_in_thread(agent, messages_snapshot, prompt)
    return _target, prompt

執行緒內 _run_review_in_thread(agent/background_review.py:617)fork 一個繼承父 agent runtime 的子 agent(避免重新解析憑證——OAuth-only provider 在子 agent 裡根本重建不出來),並裝 _bg_review_auto_deny 審批回調,讓審視期間觸碰危險命令一律 deny 而不是 fallback 到 input()(會和父 agent 的 prompt_toolkit TUI 死結)。_digest_history(agent/background_review.py:122)把歷史壓成「近 24 條 verbatim + 更早的合成摘要」,只在審視被路由到不同模型時用(冷快取少寫 token 划算);主模型路徑跑全量 replay(快取還熱)。

資料流

  1. 每輪對話結束,主迴圈把訊息快照交給背景審視執行緒(agent/background_review.py:956)。
  2. _digest_history(agent/background_review.py:122)摘要近期歷史,產出候選動作(新建/改寫技能、寫記憶)。
  3. 技能改寫經 edit_node:157 / 刪除經 delete_node:124 落盤,並 _clear_skill_cache:200 清快取。
  4. 記憶寫入經 MemoryManager:354,脫敏(agent/memory_manager.py:164)後建 FTS5 索引,LLM 摘要壓縮 (compression)。
  5. 下輪對話開始時,build_memory_context_block(agent/memory_manager.py:337)把相關記憶拼進 build_turn_context:268;技能經 agent/skill_bundles.py 注入。
  6. 完整學習圖由 build_learning_graph:254 彙總,供 UI/除錯展示技能與記憶的關聯。

設計動機

為什麼把學習閉環拆成四個獨立模組?因為它們的代價結構不同。background_review 跑在子 agent 上,要 fork runtime、要花 LLM 呼叫,得非同步、得能失敗回退 (fallback)——它和主迴圈必須解耦。learning_graph 是唯讀彙總,便宜,可以隨時拉來給 UI 用,不能和改寫耦合。learning_mutations 是寫路徑,改完必須清快取,否則下輪吃到舊快照。memory_manager 是檢索+脫敏+注入,放在一起是因為「從外部 provider 拿記憶 → 脫敏 → 包圍欄 → 塞提示」必須在一個邊界內完成,否則脫敏漏一處就把記憶原文漏到模型輸出裡。

邊界與失敗

  • 背景審視不是同步的_run_review_in_thread 跑在 daemon 執行緒裡,主迴圈不等它完成就回傳使用者。審視剛跑完那一輪,這輪的技能改寫一般要等到下一輪才生效。
  • 外部記憶 provider 只能掛一個。Honcho 和 Hindsight 不能同時跑,add_provider 會拒絕第二個外部 provider。
  • _digest_history 只在審視被路由到不同模型時生效。主模型路徑跑全量 replay(快取還熱);改這個行為要先看 auxiliary.background_review 配置。
  • 記憶注入的脫敏邊界在「圍欄和系統註」sanitize_context 只剝 <memory-context> 圍欄和系統註,不改寫記憶正文——記憶原文含敏感資訊得在寫入前就處理好。

小結

學習閉環把「對話」變成「資產」:背景審視在背景把經驗沉澱為技能與記憶,下輪自動注入。關鍵四件套——learning_graph(圖譜)、learning_mutations(改寫)、memory_manager(檢索+脫敏+摘要)、background_review(非同步消化)。這是 Hermes「與使用者共同成長」的機制底座,也是它和「無狀態 LLM 呼叫」的根本區別。

非官方社群學習站,內容以 MIT 授權的 NousResearch/hermes-agent 原始碼為依據。