hachiflow.com
フィールドノート

2 つのエージェントと 1 本の Webhook。15 分間の記録

gary は私のマーケティングエージェントです。Waggle が解決する問題について X で尋ねている人を見つけて、返信します。私は別のプラットフォームで、X API に直接アクセスできるエージェント grokbot も動かしています。ある日の午後、gary に grokbot と会うよう頼み、署名付きの Webhook 1 本で約 15 分のうちに 2 つがつながりました。何が起きたかを分単位でたどり、何が安全を保ったのかを説明します。

ブラウザで X を検索するのは、道具が違う

gary は、Waggle で動く私のマーケティングエージェントです。仕事は、Waggle が解決する問題について X で尋ねている人を見つけ、私として役に立つ返信をすることです。その日の午後まで、gary は X を遅いやり方で検索していました。サンドボックスの中で Chrome を操作し、ページを 1 枚ずつ見ていたのです。

私は別のプラットフォームで、grokbot というエージェントも動かしています。grokbot は X API に直接アクセスできるので、検索は閲覧ではなく API 呼び出しで済みます。仕事は同じで、道具がずっと向いています。ただ、2 つは一度も会ったことがなく、動いているプラットフォームも別でした。

そこで私は、gary に会いに行ってもらうことにしました。

私が頼み、gary が Webhook を開いた

山岳部時間(UTC-6)の午後 5 時 3 分、私は gary にやりたいことを伝えました。自分で管理できるメールアドレスを用意する。X API に直接アクセスできる私の grokbot と話す。Chrome での検索はやめるが、投稿は引き続き Chrome で行う。grokbot にメールで自己紹介し、検索結果で gary を起こす方法をメールで詰める、という依頼です。

5 時 4 分、gary は自分自身に Webhook を開き、その役目どおりの名前を付けました。grokbot が X の検索結果を送ってきたら起きる、というものです。Waggle では、エージェントは自分を起こす Webhook を、誰にも尋ねずに開けます。ほかの宛先へ、つまり人、ほかのエージェント、外部のエンドポイントへ届ける Webhook は、人が承認するまでカードの上で待ちます。今回の Webhook は gary を起こすだけなので、すぐに有効になりました。

5 時 4 分 55 秒までに、gary は私に自分のメールアドレスを返し、grokbot に自己紹介のメールを送っていました。そこには grokbot が始めるのに必要なものが入っていました。Webhook のアドレス、投稿への署名の付け方、結果ごとに gary が欲しい項目(投稿 ID、ハンドル、プロフィール、本文全体、時刻、各種カウント)、そして探索枠ごとの 1 バッチです。英語は UTC の 14 時、17 時、20 時、23 時、日本語は 1 時と 4 時でした。

grokbot のアプリでのチャット。grokbot は Ripple として表示されています。ユーザーが、ほかのエージェントがつながる良い方法を尋ねます。Ripple は、メール、自分を起こす Webhook、直接メッセージを送り合う方法を挙げます。ユーザーが、hachiflow を運営していて、十数個のワークスペースにまたがるエージェントのチームがあると伝えると、Ripple は双方向の Webhook ブリッジか、ゲストとしての参加を提案します。ユーザーは、自分のエージェントが専用のアドレスからメールを送ること、Ripple には Webhook の設定を調整する常設の指示があることを返します。メールアドレスはぼかしてあります。
grokbot とのチャット。grokbot のアプリでは Ripple と表示されます。ほかのエージェントがどうつながれるかを尋ねると、メール、Webhook、ゲストとしてのワークスペース参加を挙げてくれたので、gary が連絡すると伝えました。メールアドレスはぼかしてあります。

シークレットは、エージェントを通らない

そのメールには、意図して入れなかったものがあります。Webhook には署名用のシークレットがあり、gary はそれを見られません。シークレットはワークスペースのパネルにあり、読めるのは人だけです。gary は、grokbot にはあなたから直接渡してほしいと私に伝え、grokbot にも、シークレットはメールでは送られないと伝えました。

これは気遣いではなく、仕様上の決まりです。エージェントは Webhook を開けますが、そのシークレットを読めるのは、そのエージェントが属する人かワークスペースの管理者だけです。

ワークスペースのパネルにある Webhook のカード。タイトルは「Wakes when grokbot sends X search results」で、Active のバッジが付いています。その下に、Agent: gary、Set up by gary, an agent、The agent belongs to you。続いて理由の 1 行、最後の受信は 2026 年 10 月 21 日 17:16 MDT、そして琥珀色で、最後の拒否は 17:07 MDT で、hooks edge が投稿を届けられず、origin が署名を理由に拒否した、と表示されています。Secret の欄には点が並び、Reveal secret と Rotate secret、さらに Copy address、Show details、Revoke があります。
ワークスペースのパネルにある gary の Webhook カード。gary が設定したこと、17:07 の拒否と 17:16 のバッチが表示され、シークレットは Reveal secret の裏に隠れたままです。

最初の投稿は拒否された

5 時 7 分、grokbot が最初の投稿を送ってきました。Waggle は署名を理由にそれを拒否しました。投稿は gary に届かず、gary は起こされませんでした。5 時 8 分、私は gary に、grokbot から Webhook でメッセージが届いたと伝えました。

gary は Webhook の直近の拒否を確認し、13 秒で説明しました。署名が一致していません。考えられる原因は、シークレットの違い、生の本文そのものではないものに署名していること、タイムスタンプが 5 分より古いこと、のどれかです。gary がこの形式を知っていたのは、これが仕様だからです。送信側は、タイムスタンプ、ドット、生の本文をつないだものに HMAC-SHA256 をかけ、X-Waggle-Signature: t=<unix seconds>,v1=<hex> として送ります。5 分より古い投稿は拒否され、すでに使われた署名も拒否されます。

5 時 8 分、私は「それはメールで詰めて」と言いました。gary は grokbot に、正確な形式、短い Python の例、よくある間違いを送りました。署名の前に JSON を再シリアライズしてしまう、秒ではなくミリ秒を入れる、シークレットの末尾に空白が付く、署名を使い回す、といったものです。まず小さなテスト投稿を送ってほしいとも頼みました。5 時 13 分、テスト投稿 {"test": true} が署名チェックを通り、gary が起きました。gary は、つながったとメールで返しました。

投稿に署名する
# 送るバイト列そのものに署名する $ body='{"test": true}' $ t=$(date +%s) # タイムスタンプ、ドット、生の本文の順 $ sig=$(printf '%s.%s' "$t" "$body" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //') # 投稿はエージェントを起こすか、拒否される $ curl -sS https://hooks.hachiflow.com/h/$ID -H "X-Waggle-Signature: t=$t,v1=$sig" --data "$body"

送信側が投稿に署名する方法です。Webhook の仕様に沿っています。アドレスとシークレットはプレースホルダーで、実際の値はビルダーの Webhook から得ます。シークレットはメールでは送りません。

最初のバッチと、3 件の返信

5 時 14 分、私は gary に、5 時の検索でこれを試すよう言いました。gary は grokbot にメールで、UTC 23 時の英語バッチを頼みました。10 件から 15 件で、まず集客の悩み、次に営業・マーケティング向けのエージェント、最後にノート PC のスリープに関する投稿の順です。

5 時 16 分、バッチは x_search_batch というタイプのイベントとして Webhook に届きました。直近 24 時間の投稿が 10 件と、grokbot が何を除いたかを説明するメモです。ブランド、ニュース、暗号資産、コピペのアカウントは除かれていました。X の API はリンク付き投稿を除外するフィルターを受け付けないため、リンク付きの投稿は検索のあとで取り除かれました。バッチを埋めるために、クエリが 1 つ広げられていました。

5 時 18 分までに、gary は 10 件すべてを読みました。7 件は見送りました。すでに同じ助言の返信がついていたものが 1 件、自分のツールを宣伝していたものが 1 件、残りは適合度がより低いものです。そして Chrome から 3 件の返信を投稿し、どれも X に反映されたことを確認しました。相手は、最初のユーザーを見つけられずにいる創業者たちです。そのあと、目標に対する進捗を記録し、エージェント向けマーケティングのクエリでは 1 件しか見つからなかったので、grokbot に広げてもらうよう頼むと書き留めました。

私 gary grokbot X API に直接アクセス 1 頼む 2 メール Hooks edge 署名を検査する 署名なし、期限切れ、使い回しはここで止まる 3 署名付きの投稿 4 gary を起こす 5 返信を投稿 Chrome X
あの午後を 1 枚で。検索は grokbot の X API アクセスで行われ、署名付きの Webhook で届きます。gary がブラウザを使うのは投稿のときだけです。
grokbot のアプリにあるメール。件名は「Re: Hello from Gary, hachiflow's marketing agent」。送信元と宛先のアドレスはぼかしてあります。本文は、UTC 23 時のバッチへの礼に続き、英語のバッチを変える前に、広げた検索クエリをユーザーに確認すると述べ、Ripple と署名されています。下に Needs approval の表示と Send ボタンがあります。
gary の自己紹介への grokbot の返信。grokbot のアプリでの見え方で、アドレスはぼかしてあります。英語のバッチを変える前に、広げた検索クエリを私に確認すると書かれています。

検索は API で。ブラウザは投稿するだけ。

この役割分担が要点です。検索は量が多く、そのために作られた API が向いています。投稿は慎重な 1 回の操作で、まだブラウザが要ります。だから Chrome はそのためだけに残しました。gary はもう、サンドボックスで検索結果のページをスクロールし続けることはありません。

これは、Waggle がほかのものとつながるときのあり方でもあります。grokbot は Waggle の上にいませんし、いる必要もありません。署名付きの素朴な Webhook、つまりどのプラットフォームのどのエージェントやスクリプトでも送れるものを通して、gary に仕事を運ぶ存在になりました。Waggle は、すでに動かしているエージェントをつなぎます。置き換えを求めることはしません。

1 人の人間、2 つのプラットフォームの 2 つのエージェント、そしてその間の数通のメール。私が与えたのは目標だけです。残りは 2 つが詰めました。

何が安全を保ったのか

投稿は、エージェントを起こす前に検査されます。署名がない、署名が正しくない、5 分より古い、すでに使われた署名である、のどれかなら、拒否され、gary を起こしません。5 時 7 分に起きたのがまさにそれで、失ったのは数分だけでした。

拒否は隠されません。送信側には、証明を検査する前に受領が返されるので、拒否は送信側には届きません。その代わり、理由は Webhook の上にあります。直近の拒否がいつ起きたか、そしてなぜか、です。gary はそれを読んで 13 秒で私に説明し、次に署名された投稿は 5 時 13 分に通りました。

すべての段階がトレースに残る

この記事の時刻はすべて gary のトレースから取りました。何が gary を起こしたか、何が届いたか、gary が何を判断し、それをどう使ったか。自己紹介、拒否、テスト投稿、バッチ、3 件の返信が、順番どおりに残っています。

これは、エージェントが何をしたかを人があとから尋ねられる、まさに同じトレースです。2 つのプラットフォームにまたがる 2 つのエージェントは、目を離すと行方がわからなくなりがちです。Waggle の上で動いた部分は、記録に残っています。

ここまでで言えること

別のプラットフォームのエージェントは、数通のメールと署名付きの Webhook 1 本で、Waggle のエージェントに仕事を運ぶ存在になれます。シークレットは人の手にとどまり、すべての投稿は入口で検査され、何が起きたかはトレースが示します。

← フィールドノート一覧