Hermes Agent 入門 — 初心者のためのハンズオンガイド|2026-09 版
第 12 章

MLOps — 学習データ生成基盤としての Hermes

バッチトラジェクトリ生成・ShareGPT 形式・トラジェクトリ圧縮・Atropos 連携を俯瞰する

この章で分かること

これは何か

ここまでの章で扱ってきた機能は、どれも入口が明快だった。CLI のサブコマンド、対話中のスラッシュコマンド、エージェントが呼ぶツール、あるいは ~/.hermes/config.yaml の設定キーのどれかから触れた。この章の機能はそのどれでもない。batch_runner.py と trajectory_compressor.py は、クローンした hermes-agent リポジトリの直下に置かれた独立した Python スクリプトであり、hermes コマンド自体には対応するサブコマンドも設定キーも存在しない。実行するには、通常の hermes の代わりにリポジトリのルートで python batch_runner.py ... のように直接スクリプトを叩く。

この 2 本のスクリプトが扱うのは、エージェントの会話を丸ごと記録した「トラジェクトリ」というデータである。batch_runner.py は JSONL 形式のプロンプト集合を並列にエージェントへ流し込み、できあがった会話履歴を ShareGPT 互換の JSONL として data/<run_name>/ 以下に書き出す。主目的はモデルのファインチューニング用データや評価用データを大量に作ることで、日々の対話 1 回とは性質が違う。trajectory_compressor.py は、そうしてできたトラジェクトリを後から加工し、決められたトークン予算に収まるように中間部分だけを圧縮する。もう 1 つ、通常の対話 1 回分のトラジェクトリを保存したいだけなら run_agent.py --save_trajectories、あるいはライブラリとして使う場合は AIAgent(..., save_trajectories=True) という形でも有効化できる(これも hermes CLI には露出していないライブラリレベルのスイッチである)。バッチランナーはこの保存を常時オンで行う。

~/.hermes/ 配下の全体像は 第 2 章を参照してほしい。この章で扱うファイル群はその外側、リポジトリの作業ディレクトリ側に生成される点も他の章との違いである。

いつ使うのか

この機能が要るのは、次のような場面に限られる。

いずれも「今の会話をどう進めるか」ではなく「後から使う学習・評価データをどう作るか」という話であり、この章で扱う機能は今の会話そのものには影響しない。似た言葉が出てくる隣接機能とは目的が根本的に違うので、混同しないよう次の表で切り分ける。

機能並列化の対象目的結果の使い道
バッチトラジェクトリ生成(本章)データセット中の多数のプロンプト学習・評価データの一括生成ファインチューニングや Atropos RL への入力
delegate_task バッチ(第 9 章)今の会話中の複数タスク今のセッションの作業を並列化親の会話に要約が戻り、そのまま使われる
cron スケジューラ(第 8 章)時間軸上で繰り返す単発ジョブ定期実行・自動レポート指定した配信先に都度送られる

迷ったら判断は単純で、「作っているのが今すぐ使う会話の結果か、後で別のモデル学習に使うデータか」で決まる。後者ならこの章の出番である。

バッチトラジェクトリ生成のパイプライン

batch_runner.py は JSONL データセットを 1 行 1 プロンプトとして読み込む。各エントリは最低限 prompt フィールドを持ち、任意で image/docker_image(プロンプトごとに使うコンテナイメージ。Docker・Modal・Singularity バックエンドで有効)や cwd を指定できる。

{"prompt": "Write a Python function that finds the longest palindromic substring"}
{"prompt": "Compile this Rust program and run it", "image": "rust:1.75"}

実行は次のような形になる。--num_workers が同時に走らせるワーカープロセス数、--batch_size が 1 バッチあたりのプロンプト数、--max_turns(既定 10)が 1 プロンプトあたりの最大ツール呼び出し往復数を決める。

python batch_runner.py \
    --dataset_file=data/prompts.jsonl \
    --batch_size=10 \
    --run_name=my_first_run \
    --model=anthropic/claude-sonnet-4.6 \
    --num_workers=4

各プロンプトには、あらかじめ用意された「ツールセット分布(distribution)」からランダムにツール構成がサンプリングされる。現行の実装ではツールセットごとに独立した有効化確率が割り当てられ、最低 1 つは必ず有効になることだけが保証される(手作りの組み合わせ表ではなく、確率的な独立サンプリングである点に注意)。使える分布の一覧は --list_distributions で確認できる。

batch_size 単位 完了分を統合 エクスポート resume 時に内容照合 prompts.jsonl (データセット) 並列ワーカー (num_workers 並列) trajectories.jsonl checkpoint.json statistics.json (外部) fine-tuning/RL
図 12-1 batch_runner.py はデータセットを並列ワーカーへ投入し、完了分を trajectories.jsonl 等に統合する。checkpoint.json は再開(--resume)時の照合に使われ、最終出力は hermes-agent の外(ファインチューニングや Atropos RL)で消費される。

途中で止まっても、--resume を付けて再実行すれば続きから再開できる。ここでの「続きから」は行番号ではなく内容そのものの一致で判定される。既存の batch_*.jsonl を全部スキャンし、完了済みのプロンプトをテキスト内容で照合したうえで、まだ終わっていない分だけを再バッチして処理する。そのため、データセットの行順が変わっても正しく再開できる一方、失敗したプロンプトだけは自動的に再試行対象に残る。最終的にはすべてのバッチファイル(旧実行分も含めて)が 1 本の trajectories.jsonl にマージされる。

生成された結果には自動の品質フィルタがかかる。推論皆無フィルタは、全ターンを通じて推論内容(ネイティブの thinking トークンも <REASONING_SCRATCHPAD> も)が一切無いサンプルを破棄する。破損エントリフィルタは、最終マージ時に幻覚のツール名(有効なツール一覧に無い名前)を含むエントリを除外する。つまり、投入したプロンプト数と最終的な trajectories.jsonl の行数は一致するとは限らない。

トラジェクトリ形式(ShareGPT 互換)

トラジェクトリは agent/trajectory.py・run_agent.py(_save_trajectory)・batch_runner.py のいずれから保存された場合も、共通の JSONL 形式を使う。対話 CLI からの単発保存は trajectory_samples.jsonl(完了分)と failed_trajectories.jsonl(失敗・中断分)に、バッチランナーからは batch_N.jsonl 経由で書かれる。中身の会話部分(conversations 配列)は ShareGPT の呼び方に変換される。

API 上のロールShareGPT の from
system"system"
user"human"
assistant"gpt"
tool"tool"

推論内容はすべて <think> タグに正規化される。ネイティブの thinking トークンを持つプロバイダの場合はその内容をそのまま包み、<REASONING_SCRATCHPAD> 形式で推論している場合は変換される。推論が一切無かったターンにも空の <think></think> が挿入され、どのサンプルも同じ形式になるよう統一される。ツール呼び出しは <tool_call>{"name": ..., "arguments": ...}</tool_call> という XML 包みの JSON に変換される。

{"from": "gpt", "value": "<think>\npython3 --versionを実行して確認する\n</think>\n<tool_call>\n{\"name\": \"terminal\", \"arguments\": {\"command\": \"python3 --version\"}}\n</tool_call>"}

バッチランナー版のエントリには、さらに tool_stats・tool_error_counts が付く。これらは実際に使われなかったツールも含め、全ツールをゼロ既定値で網羅するよう正規化されている。理由は HuggingFace Datasets 側で Arrow スキーマの不一致エラーを起こさないためで、読み込み側は load_dataset("json", data_files=...) でそのまま扱える。

トラジェクトリ圧縮

生成したトラジェクトリが長すぎて学習パイプラインのトークン予算に収まらないとき、trajectory_compressor.py で圧縮する。

python trajectory_compressor.py --input=data/my_run
python trajectory_compressor.py --input=data/trajectories.jsonl --target_max_tokens=16000

圧縮の戦略は「全部を均等に削る」のではなく、学習信号として重要な部分を保護しながら中間だけを削る作りになっている。

  1. 先頭のターン(system・human・最初の gpt・最初の tool)は必ず保護する
  2. 末尾の N ターン(最終的な行動と結論)も必ず保護する
  3. 2 番目の tool 応答以降、中間のターンだけを圧縮対象にする
  4. 目標のトークン数に収まる分だけ圧縮し、削りすぎない
  5. 圧縮した区間は 1 つの human サマリーメッセージに置き換える
  6. 残りの tool call はそのまま保持し、モデルがサマリーの後も作業を続けられるようにする
system human gpt1 tool1 gpt2 tool2 gpt3 tool3 gptN 保護(先頭) 圧縮対象(中間) 保護(末尾) system human gpt1 tool1 human 要約 gptN target_max_tokens 以内に収まるまで圧縮する
図 12-2 圧縮前(上段)は先頭 4 ターンと末尾のターンを保護し、2 番目の tool 応答以降の中間ターンだけを対象にする。圧縮後(下段)はその中間区間を 1 件の human 要約メッセージに置き換え、残りの tool call はそのまま残す。

--sample_percent を指定すると全件ではなく一定割合だけを圧縮対象にできる。付属の scripts/sample_and_compress.py は、HuggingFace 上の複数の公開データセット(例: NousResearch/swe-terminus-agent-glm-kimi-minimax)からサンプリングし、トークン予算に収まるよう圧縮するためのユーティリティである。

Atropos RL 連携と役割分担

公式トップページは Hermes Agent の特徴の 1 つとして次のように書いている。

"Research-ready — Batch processing, trajectory export, RL training with Atropos."

この一文だけを読むと、強化学習(RL)の学習コード自体が hermes-agent の中に入っているように見えるが、それは誤りである。学習パス「I want to train models」の推奨順序を見ると、実態がはっきりする。

  1. Quickstart
  2. Configuration
  3. Atropos RL Environments(公式ドキュメントに (external) と明記されている)
  4. Provider Routing
  5. Architecture

Atropos の実装コードは hermes-agent リポジトリの中には無く、NousResearch/atropos という別リポジトリで公開されている。hermes-agent 側が担うのはここまでで説明してきた、ShareGPT 形式のトラジェクトリを生成・圧縮するところまでであり、それを強化学習環境としてどう使うかは Atropos 側の責務になる。

別リポジトリの境界 hermes-agent (このリポジトリ) batch_runner.py(生成) trajectory_compressor.py(圧縮) trajectories.jsonl(出力) atropos (NousResearch/atropos・外部) RL 環境定義 学習ループの実装 エクスポート hermes-agent 内に RL 学習コードは無い
図 12-3 hermes-agent が担うのはトラジェクトリの生成・圧縮まで。RL 環境の定義や学習ループの実装は NousResearch/atropos という別リポジトリにある。

ハンズオン 12-1 データセットとバッチ生成コマンドの対応を読み解く

  1. 手元に、1 行 1 プロンプトの最小データセットを用意する(実行はまだしない)。
{"prompt": "Write a Python function that finds the longest palindromic substring"}
  1. クローンした hermes-agent リポジトリのルートで、モデル呼び出しを一切伴わないコマンドを実行し、使えるツールセット分布を確認する。
python batch_runner.py --list_distributions

これは API キーもモデルへの呼び出しも必要としない、ローカルな一覧表示コマンドである(表示される分布名の具体的な内容はインストールしているバージョンに依存するため、ここでは再現しない)。

実際にバッチを回すときの課金に注意 — 上記の --list_distributions と違い、--dataset_file を指定した本番のバッチ実行はモデルへの実 API 呼び出しを伴う。num_workers 個のワーカーがそれぞれ独立した課金対象のエージェントセッションを同時に走らせるため、コストは実質的に「プロンプト数 × max_turns」に比例して積み上がる。試すときは --max_samples でデータセットの先頭数件だけに絞ってから本番の件数へ広げるとよい。

用意した 1 行のデータセットに対して本番実行する場合の全体像は次のとおりで、出力は data/<run_name>/trajectories.jsonl・checkpoint.json・statistics.json に分かれる。途中で止めても --resume を付ければ、内容が一致する完了済みプロンプトをスキップして続きから進む。

つまずきポイント

「RL training with Atropos」を hermes-agent 内に学習コードがあると読み違えない — README や公式トップページの謳い文句はテンポよく並んでいるため、Atropos による RL 学習まで hermes-agent が一貫してやってくれるように見えてしまう。実際には hermes-agent が作るのは ShareGPT 形式のトラジェクトリまでで、それを使った RL 環境の構築・学習ループの実装は別リポジトリ NousResearch/atropos の役割である。

トラジェクトリ保存は既定でオンではない — 通常の対話 CLI でエージェントと話しているだけでは、トラジェクトリは保存されない。単発の対話で保存したいなら run_agent.py --save_trajectories のようにオプトインする必要があり、hermes コマンドにこれに対応するフラグは無い。常時保存されるのはバッチランナー経由で実行したときだけである。

投入したプロンプト数と出力行数は一致するとは限らない — 推論皆無フィルタと破損エントリフィルタが自動的にサンプルを間引くため、trajectories.jsonl の行数はデータセットの行数より少なくなり得る。statistics.json の破棄件数を確認せずに「n 件生成したはず」と思い込むと数が合わない。

resume は行番号ではなく内容一致で判定される — --resume は既存の batch_*.jsonl をプロンプトの実際のテキストで照合する。データセットに全く同じ文面のプロンプトが複数行あると、どちらが完了済みでどちらが未処理かをテキストだけでは区別できず、意図しない形でスキップされることがある。

理解度チェック

Q1. バッチトラジェクトリ生成は hermes CLI のどのサブコマンドから実行しますか。

どのサブコマンドからも実行しない。hermes CLI には対応するサブコマンドも設定キーも無く、クローンしたリポジトリのルートで python batch_runner.py を直接実行する。

Q2. ShareGPT 形式で assistant のメッセージはどのロール名に変換されますか。

"gpt"。system は "system"、user は "human"、tool は "tool" に変換される。

Q3. トラジェクトリ圧縮で必ず保護され、圧縮対象にならないのはどの部分ですか。

先頭のターン(system・human・最初の gpt・最初の tool)と、末尾の N ターン(最終的な行動と結論)。圧縮対象になるのは 2 番目の tool 応答以降の中間ターンだけである。

Q4. Atropos による強化学習の学習ループを実装しているコードはどこにありますか。

hermes-agent リポジトリの外、NousResearch/atropos という別リポジトリにある。hermes-agent 側が担うのは、その入力となる ShareGPT 形式のトラジェクトリを生成・圧縮するところまでである。

まとめ

参考リンク