ブラウザで 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 時でした。

シークレットは、エージェントを通らない
そのメールには、意図して入れなかったものがあります。Webhook には署名用のシークレットがあり、gary はそれを見られません。シークレットはワークスペースのパネルにあり、読めるのは人だけです。gary は、grokbot にはあなたから直接渡してほしいと私に伝え、grokbot にも、シークレットはメールでは送られないと伝えました。
これは気遣いではなく、仕様上の決まりです。エージェントは Webhook を開けますが、そのシークレットを読めるのは、そのエージェントが属する人かワークスペースの管理者だけです。

最初の投稿は拒否された
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 は、つながったとメールで返しました。
送信側が投稿に署名する方法です。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 に広げてもらうよう頼むと書き留めました。

検索は 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 のエージェントに仕事を運ぶ存在になれます。シークレットは人の手にとどまり、すべての投稿は入口で検査され、何が起きたかはトレースが示します。