Skip to content

学習ループと記憶

源码版本v2026.7.20

職務

これは Hermes が普通の agent と一線を画す核心:「使うほど良くなる」ループ。毎ラウンドの対話を次のように沈殿させる:① スキル進化——経験からスキルを作成/書き換え(agent/learning_mutations.py)。② 記憶——FTS5 全文検索 + LLM 要約による過去対話検索(memory_manager)、Honcho の弁証式ユーザーモデリングと組み合わせる。③ 学習グラフ——スキルと記憶をノード/辺に構成(agent/learning_graph.py)。④ バックグラウンド審視——background_review がスレッドで履歴を消化し、実行可能な書き換え動作を産出する。スキル書き換えは最終的にスキルシステムに還流し、後続ラウンドで再利用される。

主要ファイル

SkillNode と学習グラフ

スキル/記憶書き換え

記憶管理

バックグラウンド審視

SkillNode の姿

SkillNode は学習グラフにおけるスキルの統一表現(agent/learning_graph.py:28-41)だ。dataclass のフィールドは name/category/source(base 内蔵か profile ユーザー/agent 作成か)/use_count(.usage.json から読む)/created_by('agent' はバックグラウンド審視が作成)/pinned/relatedbuild_skill_nodesskills/ 下の 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 はツールスナップショットも一緒にクリアする——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 に渡す。これによりテスト層が threading.Thread を patch しても動く。関数シグネチャ(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) は親 agent の runtime を継承する子 agent を fork する(証憑の再解析を避けるため——OAuth-only provider は子 agent では再構築できない)。さらに _bg_review_auto_deny 承認コールバックを装備し、審視中に危険コマンドに触れたら一律 deny とし、input() にフォールバック (fallback) しない(親 agent の prompt_toolkit TUI とデッドロックする)。_digest_history(agent/background_review.py:122) は履歴を「直近 24 件 verbatim + それより前の合成要約」に圧縮 (compression) する。これは審視が別のモデルにルーティングされたときだけ使われる(コールドキャッシュでは 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 要約で圧縮する。
  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 で走り、runtime を fork し、LLM 呼び出しを費やす。非同期でなければならず、失敗時のフォールバックが要る——主ループと疎結合でなければならない。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 の「ユーザーと共に成長する」機構の基盤であり、Hermes と「ステートレスな LLM 呼び出し」の根本的な違いでもある。

非公式コミュニティ学習サイト。MIT ライセンスの NousResearch/hermes-agent ソースに基づく。