0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Agent が育てた skill は2度と公式 update を受けられない: Hermes Agent の skill ecosystem 運用 trap

0
Last updated at Posted at 2026-05-06

Agent が育てた skill は二度と公式 update を受けられない: Hermes Agent の skill ecosystem 運用 trap

はじめに

本、記事はClaude Codeと対話しながらHermes Agentを触ってみた内容であり、以下の文章自体もClaude Codeにまとめさせたものです。

Claude Codeとの対話は、進め方をリードさせながら、実際のコマンドは私が実行して、できるだけ手触り感を把握しながら、ツッコミをClaude Codeに入れ軌道修正しつつHands-Onを進めました。そのときの気づきをギュッとQiita用にまとめさせました。ハルシネーションがあればご指摘くださいませ。

TL;DR

NousResearch の Hermes Agent には operational trap があります: agent が skill_manage(action="edit") で skill を一度自己改善すると、その skill は二度と公式 (= bundled) 側の update を受けられなくなります

これは bug ではなく設計で、.bundled_manifest というファイルが git の merge-base と同じ役割を担っているため。本記事では:

  1. fswatch で atomic write を可視化して仕組みを覗く
  2. .bundled_manifest = git merge-base アナロジーを解説
  3. trap の実害と mitigation 運用を提案

production 採用検討者は必読。

1. 前提: Hermes Agent の skill ファイルシステム

Hermes Agent には 3 つの skill 関連 storage があります:

部位 パス 役割
Bundled /opt/hermes/skills/ (= container 内) Hermes 同梱の公式 skill 群、read-only
User dir ~/.hermes/skills/ (= host から bind mount) 実際に runtime が読む場所、agent も人も書き込み可
Manifest ~/.hermes/skills/.bundled_manifest 各 skill の同期状態を <name>:<md5> 形式で記録

Hermes 起動時、内部の skills_sync.py が bundled と user dir を比較しつつ manifest を update します。これが最重要

待って、と言いたい — .bundled_manifest の役割が意外なほど深いのです。

2. fswatch で atomic write を可視化する

実際に Hermes が起動するとき、何が起きているのか? fswatch で覗くと一発で分かります:

fswatch -r ~/.hermes/skills/ | grep -v sessions

別ターミナルで docker exec hermes hermes chat -Q -q "Hi" を一発打つと、こう出ます:

~/.hermes/skills/.bundled_manifest_nzjqm5fd.tmp   ← 一時ファイル出現
~/.hermes/skills/.bundled_manifest                ← rename で確定
~/.hermes/skills/.bundled_manifest                ← metadata 更新

これは典型的な atomic write パターン:

要点:

  • .tmp ファイルに新内容を全部書く → reader は影響を受けない (= まだ本物は古いまま)
  • rename(2) で本物に置き換え → POSIX で atomic (= 同一 FS 内、bash の mv 相当)
  • 中間状態を読む reader が原理的に存在しない

bash で同じことをやるなら:

echo "新内容" > /path/to/.bundled_manifest.tmp
mv /path/to/.bundled_manifest.tmp /path/to/.bundled_manifest  # = rename(2) 呼ぶ

rename というコマンド (= Perl 系 / util-linux 系) もありますが、あれは batch リネーム用。atomic 置き換えしたいなら mv が正解。

3. atomic write の意味 (= read/write 競合回避)

なぜこんな手間をかけるのか? それは naive 実装だと:

Writer: open(本物, "w")  ← 本物が空 (truncate) になる瞬間
Reader: open(本物, "r")  ← ここで読むと「壊れた半分書きファイル」が返る 💥
Writer: 1 行目 write...
Writer: 2 行目 write...
Writer: close()

これだとレース中の reader が部分書きファイルを読む。

vs atomic write:

Writer: write to .tmp, close
Writer: rename(.tmp, 本物)  ← 不可分操作
Reader: open(本物)         ← 古い完全版 or 新しい完全版、中間なし

「壊れた状態を読む reader」が原理的に存在しない。これが atomic write の正体。gitvim の swap file、ほぼ全ての atomic な状態管理が同じパターンを使います。

4. ⭐ .bundled_manifest = git merge-base アナロジー

ここからが本題。.bundled_manifest の中身を覗くと、こうなっています:

log-triage:a3f2c1d8e5b9...
ml-project-bootstrap:7e4d8a2f9b1c...
file-organizer:c5e1b8d3f7a2...

各 skill の <name>:<md5> を記録。一見ただの index ですが、役割は git の merge-base です。

per-skill 同期 3 ルール

skills_sync.py は CLI 起動ごとに、各 skill について以下の判定をします:

3 ルール:

  • (a) 新規 bundled (= manifest に無い): user dir に copy + manifest 追記。素直。
  • (b) bundled で user dir hash == manifest hash (= 「user は触ってない」と判定): bundle が更新されていれば user dir も上書き、manifest に新 hash を記録。素直に追従。
  • (c) bundled で user dir hash ≠ manifest hash (= 「user 改変済」と判定): 両方 SKIPmanifest は改変前の origin hash のまま。これが key。

git merge-base としての解釈

ルール (c) で「manifest は改変前の origin hash のまま」が肝です。これは git の merge-base と同じ意味:

            改変前の bundle hash    ← .bundled_manifest 記録
                  │
       ┌──────────┴──────────┐
       ↓                     ↓
   bundle が新版に進化     user が edit して進化
   (= upstream branch)     (= local branch)

両 branch から 「最後に同期した点」 が manifest 値として保持される。これが git の merge-base そのもの。3-way merge の base として機能する設計です。

つまり .bundled_manifest は:

  • 「不変な snapshot」ではない
  • 「user 未改変なら追従し続ける」
  • 「user 改変したら改変前の同期点で固定

という選択的な追従機構なのです。

5. ⚠️ 運用 trap: L2 自己改善で何が起きるか

ここが本記事の警告ポイント。

Hermes Agent の L2 (= skill 自己改善) 機能は、agent が skill_manage(action="edit") を呼んで SKILL.md を上書きする仕組みです。でも skill_manage(edit) は manifest を触りません (= source code 確認済、通常のファイル書き込みのみ)。

つまり何が起きるか:

[Before] skill v1.0 (bundled が出荷した版)
         user dir hash = manifest hash (= 一致、未改変)

[Agent が L2 で skill_manage(edit) 実行]
         user dir hash 変わる
         manifest hash 変わらない

[After] user dir hash ≠ manifest hash
         → 次回 skills_sync は ルール(c) を発動
         → 「user が改変した」と判定

[upstream Hermes が bundle v1.x → v1.y に update]
         → ルール (c) で SKIP、user dir + manifest 両方据え置き
         → user dir には agent 改善版が残るが、bundle 更新は反映されない

agent が改善した瞬間から、その skill は二度と bundle 更新を受けない

副作用 1: agent edit と人編集が完全に区別不能

skills_sync.py から見ると:

skill log-triage の状況:
  user dir hash ≠ manifest hash

しかこれだけ。これが:

  • agent が L2 で改善した結果なのか
  • 人が手で SKILL.md を編集した結果なのか

区別する手段がない。両方とも同じ「user-modified」分岐に入ります。

副作用 2: bundle のセキュリティ修正が素通り

仮に bundle 側で log-triage skill にセキュリティ問題 (= 危険なコマンド実行を許してた) があり、upstream が修正版を ship したとします。user 側で過去に L2 改善されている場合、その修正は永遠に来ない

これは production 運用上のリアルなリスクです。

副作用 3: 「育てたほうが取り残される」逆説

直感的には 「使い込んだ skill ほど価値が高い」 と感じますが、Hermes の skill 機構では 「使い込んだ skill ほど upstream から取り残される」。これは autonomy を信じれば信じるほど、bundle と乖離していく構造的特性です。

6. Mitigation 運用 + まとめ

推奨 mitigation

  1. ~/.hermes/ を git 化する

    cd ~/.hermes
    git init
    cat > .gitignore <<EOF
    auth.json
    *.bak
    state.db
    state.db-shm
    state.db-wal
    .env
    sessions/
    home/
    EOF
    git add . && git commit -m "initial"
    

    → 改変履歴を別レイヤで track。manifest が判定不能でも、git log で「いつ・誰が (agent or 人)」が分かります。

  2. Skill review gate を運用に組み込む (= Human-in-the-Loop)

    • L2 後 / skill_manage(create) 後は git status で確認
    • git diff で内容 review
    • 必要に応じて手動修正 → git commit (= 承認) or git checkout (= 拒否)
  3. skill_manage(edit) 後の同期戦略を明示

    • 改善内容が production-grade なら manifest 手動 update (= 新 hash 書き込みで bundle 進化を受け続ける)
    • 改善内容が experimental なら manifest 据え置き (= bundle と切り離す)
    • これはチーム / プロジェクトごとに 明示的なポリシー化が必要
  4. upstream への提案 (= 個人的に PR 投げる予定):

    • SKILL.md frontmatter に last_edit_actor: agent|user field 追加
    • skill_manage(edit) 時に manifest も新 hash で update する option を新設
    • もしくは skills_sync.py に「agent edit は merge 候補として扱う」3-way merge 機能

まとめ

.bundled_manifest = "Hermes 同梱 skill の最後の同期点 (git merge-base 相当)"

skill_manage(edit) を呼ぶと:
  → user dir hash と manifest hash が divergent
  → skills_sync が「user 改変済」と判定
  → bundle 更新を二度と反映しない

Mitigation:
  → ~/.hermes/ を git 化
  → skill review gate を運用
  → 戦略を明示する

これは bug ではなく設計ですが、operational trap として認識してから採用判断する必要があります。Hermes Agent の autonomy を盲信せず、外部から HITL (Human-in-the-Loop) を強制する運用設計が現実解です。

検出スクリプト (= bonus)

L2 改善された skill を一覧する one-liner:

docker exec hermes /opt/hermes/.venv/bin/python -c "
import hashlib, pathlib
manifest = pathlib.Path('/opt/data/skills/.bundled_manifest').read_text()
expected = dict(line.split(':', 1) for line in manifest.splitlines() if ':' in line)

for skill_md in pathlib.Path('/opt/data/skills').rglob('SKILL.md'):
    name = skill_md.parent.name
    actual = hashlib.md5(skill_md.read_bytes()).hexdigest()
    if name in expected and actual != expected[name].strip():
        print(f'{name}: divergent (= L2 改善 or 人編集が入った)')
"

これで「bundle 同期から外れた skill 一覧」が即出ます。production 運用なら定期実行を推奨。

関連


📝 Tags: Hermes, agent, LLM, skill, git, merge-base, atomic-write, fswatch, production, mental-model

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?