Hermes Agent v0.2.0 の本当の目玉は、モデルの統合でもスキルシステムでもない。マルチプラットフォームメッセージングゲートウェイだ——一つの Hermes プロセスが、Telegram、Discord、Slack、WhatsApp、Signal、メール(IMAP/SMTP)、Home Assistant の七つのチャットプラットフォームを同時に聞いている。4 月 8 日のリリースが出る頃には、そのリストは 13 に伸びていた。途中で Matrix、飛書(Feishu)、企業微信(WeCom)、Mattermost、DingTalk、そして Twilio 経由の SMS が加わっていった。
ここで見落としやすいのは、一つの Telegram ボットを作るだけならそれほど難しくない、ということだ。難しいのは、同じエージェント、同じ記憶、同じツールレジストリを共有する七つのチャット統合を同時に動かすことで——本物のアーキテクチャ作業はそこに全部集まっている。この記事では、Hermes がこれを実際にどうやっているかを見ていく。
素朴なやり方、そしてそれがダメな理由
「Telegram でも Discord でも答える AI ボット」を作れと言われたら、最初の発想はたぶん、二つの独立したボットを立てることだろう。ボットごとに自分のプロセス、自分のデータベース、自分のステートを持つ。どちらも同じ言語モデル API を叩く。Telegram のユーザーと Discord のユーザーは概念上は同じエージェントと話しているはずなのに、実際にはお互いを知らない二つのエージェントと話していることになる。
既存のボットプロジェクトの多くはこの形だ。そしてこのやり方は、しばらく経ってから効いてくるところで本当にひどい。
- •記憶が分岐する。 Telegram でピーナッツアレルギーだと伝えたユーザーが、Discord で料理の質問をしたときには、その事実はもう消えている。同じことをエージェントに二回教え直すはめになる。
- •ツールのステートがずれる。 Slack 経由で仕込んだ cron ジョブが、Telegram で cron を確認したときには見えない。セッションの履歴は分断される。共有リソース(たとえば Notion 統合)の認証トークンは、ボットごとに別々にインストールしなければならない。
- •運用の手間が倍々になる。 ボットが N 個ということは、プロセスが N 個、サービスが N 個、ログストリームが N 個、設定ファイルが N 個、壊れうる場所が N 箇所ということだ。複雑さはプラットフォーム数に比例して増えていく。
- •安全ルールが割れる。 一つのボットで危険コマンドの承認ポリシーを締めたら、他のボットでも同じ更新をしないと揃わない。セキュリティのドリフトが既定値になる。
Hermes は別の選択をした。エージェントプロセスはただ一つ、セッションデータベースもただ一つ、メモリストアもただ一つ、ツールレジストリもただ一つ。プラットフォームはアダプタ——共有エージェントにメッセージを出し入れするための薄い玄関でしかない。
ゲートウェイの形
Hermes のゲートウェイは内部的に三層構成になっている。
一番下にいるのがエージェント本体だ——CLI モードで動くのと同じ Hermes コア。こちら側はチャットプラットフォームの存在を一切知らない。メッセージを受け取り、エージェントループ(LLM 呼び出し、ツール呼び出し、記憶参照、チェックポイント)を回し、応答を返す。外界に向けて公開しているインターフェースは、キューベースの API ひとつだけだ。
真ん中にいるのがゲートウェイコアだ。セッション管理、ユーザー/プラットフォームのルーティング、承認ディスパッチ、cron スケジューリング、ストリーミング、メディア処理。「プラットフォーム X のユーザー Y が、チャンネル Z にメッセージを送ってきた」を「セッション S で、この権限セット付きのエージェント実行」に翻訳するのがここの仕事だ。同時に、プラットフォーム横断の共通関心事も全部ここで面倒を見る——レート制限、フラッド制御、メッセージの重複検知、無操作ベースのタイムアウト、承認ボタンのルーティング、プラットフォームをまたぐ状態。
一番上にいるのがプラットフォームアダプタ。プラットフォームごとに一つずつ——Telegram アダプタ、Discord アダプタ、Slack アダプタ、以下同様。アダプタの仕事は狭い。自分のプラットフォームに繋ぎ(ポーリング、ロングポーリング、WebSocket、webhook、あるいはそのプラットフォームの SDK が好む形で)、入ってくるプラットフォーム固有のイベントをゲートウェイ内部のメッセージ形式に翻訳し、ゲートウェイから出ていく応答をプラットフォームが受け取れる形(Markdown、MarkdownV2、mrkdwn、Discord のリッチエンベッド、Matrix HTML、Slack blocks、メールの MIME)に翻訳し直す。
アダプタは意図的に小さく作ってある。新しいプラットフォームを追加するのは(v0.4.0 でコミュニティのコントリビュータが Mattermost を 300 行未満の Python で加えた)ほとんどが、その SDK のイベントをゲートウェイ内部のメッセージ形状に対応付けて、逆方向にも対応付ける、それだけの作業だ。
セッションはプラットフォームをまたぐとどう振る舞うのか
Hermes のセッションは、自分の記憶ウィンドウ、ツールの状態、実行履歴を持った会話スレッドのことだ。単一プラットフォームの中では対応関係は素直だ——Telegram のチャット一つがセッション一つ、Discord のチャンネルが一つ、Slack のスレッドが一つ。プラットフォームをまたぐと、ここから少し面白くなる。
既定では、Hermes はプラットフォームとチャットの組み合わせをそれぞれ別のセッションとして扱う。Telegram のボットとの DM、Slack でボットと立てたスレッド、Discord のチャンネルでのボットとのやり取り——この三つは、それぞれ独立した実行コンテキストを持つ三本の別会話だ。ただし、長期記憶は差し替え可能なメモリプロバイダ(v0.7.0 以降の既定は Honcho)を通じて共有される。だから、セッションを越えて持ち越してほしい情報——「このユーザーはピーナッツアレルギーだ」「このユーザーの名前は Alice」「このユーザーのプロジェクトは Atlas」——は記憶レイヤーの上に載っている。一方で、各会話の短期コンテキストは、今使っているそのプラットフォームのスコープに閉じたままだ。
これが、同じアシスタントがどのプラットフォームで使っても同じアシスタントに感じられる、しかし全メッセージが絡み合ったグローバル放送には化けない、その設計のキモだ。
スレッド内のユーザー別セッションがなぜ重要か
v0.4.0 で Hermes は 既定でスレッド内をユーザー別セッションに分ける機能を追加した——グループチャットでは、同じスレッドの中でもユーザーごとに自分のセッションを持つということだ。小さく聞こえるかもしれない。だが、マルチユーザーのボットをグループに入れたことがある人にとっては、これは大きな話だ。
グループチャットでユーザー別セッションがないと、すべてのメッセージが一つの共有会話の中に入っていくことになる。Alice がボットに質問して、その 30 秒後に Bob が別の質問をすると、ボットのコンテキストはもう二人分がごちゃ混ぜになった塊だ。答えが混線する。Alice の記憶に Bob のデータが混入する。ユーザーが増えるほどゲートウェイモードの体験は悪化していく。
スレッド内ユーザー別セッションがあれば、Alice も Bob も同じスレッドの中でそれぞれ自分専用のセッションを持つ。画面上ではお互いのメッセージが見えているのに、エージェントはユーザーごとに別々のコンテキストと別々の記憶書き込みを保つ。v0.8.0 からこれは全ゲートウェイプラットフォームの既定動作になった。別のやり方で一度痛い目を見るまではそのありがたみが見えない類の修正で、一度見えてしまうと、もう戻れなくなる。
このゲートウェイは結局なんのためのものか
このアーキテクチャを十分に眺めていると、ゲートウェイが「チャットプラットフォーム上でボットを動かす仕組み」に見えなくなってきて、本当の姿が浮かんでくる——人間(その人がたまたま開いているアプリがどれであれ)と、共有の記憶と共有のツールを持つ唯一の AI アシスタントを結ぶ協調レイヤーとしての姿だ。
チャットプラットフォームはプロダクトではない。入口だ。プロダクトはその裏にいるアシスタントであり、自分が今どの入口から入ったかを意識しなくていいという事実そのものだ。
これが Hermes が初日に出荷した機能だった。そのあとの四週間に起きたことは全部、この協調レイヤーが新しい振る舞いを少しずつ覚えていった物語だ。