メインコンテンツへスキップ

概要

Daemon WebSocket API は、Daemon クライアントがリアルタイムでメッセージを受信・処理するための永続的な双方向接続を提供します。クライアントがポーリングする REST API とは異なり、WebSocket 接続により Shannon は受信メッセージ(Slack、LINE、またはシステムイベントから)を接続済みの Daemon に直接プッシュできます。 このプロトコルの中核は Claim ベースのメッセージディスパッチモデルです。Shannon は対象となるすべての接続にメッセージをブロードキャストし、Daemon が排他的処理権を競い合ってから応答します。

エンドポイント

GET /v1/ws/messages
標準の認証 ヘッダー を使用して WebSocket にアップグレードします。

認証

認証は WebSocket アップグレードの前に、REST Endpoint と同じミドルウェアで実行されます。
方式ヘッダー
JWT BearerAuthorization: Bearer <token>
API KeyX-API-Key: <key>
websocat "ws://localhost:8080/v1/ws/messages" \
  -H "Authorization: Bearer <token>"

接続ライフサイクル

1

HTTP アップグレード

クライアントが認証 ヘッダー 付きで GET /v1/ws/messages を送信します。サーバーは資格情報を検証してからアップグレードを行います。
2

WebSocket 確立

サーバーが WebSocket にアップグレードします(gorilla/websocket、4KB 読み書きバッファ、CheckOrigin はすべてのオリジンを許可)。
3

接続確認

サーバーが connected メッセージを送信して、接続の準備完了を確認します。
{"type": "connected"}
4

双方向メッセージング

双方が JSON メッセージを交換します。サーバーが受信メッセージをディスパッチし、クライアントが Claim、処理、応答を行います。
5

キープアライブ

サーバーは 20 秒ごとに WebSocket Ping を送信します。クライアントは 60 秒以内に Pong で応答する必要があり、応答がない場合は接続が切断されます。

接続パラメータ

パラメータ
Ping 間隔20s
Pong タイムアウト60s
最大メッセージサイズ64 KB
書き込みタイムアウト10s
読み書きバッファ4 KB

メッセージエンベロープ

すべてのメッセージ(双方向)は統一されたエンベロープ形式に従います:
{
  "type": "<message_type>",
  "message_id": "<uuid>",
  "payload": {}
}
フィールド説明
typestringメッセージタイプ識別子
message_idstring (UUID)一意のメッセージ識別子(connected および disconnect では省略)
payloadobjectタイプ固有のデータ

サーバーからクライアントへのメッセージ

connected

WebSocket 接続の確立直後に一度だけ送信されます。
{
  "type": "connected"
}

message

処理のためにディスパッチされたインバウンドメッセージ。これは主要なメッセージタイプで、Channel Webhook(Slack、LINE)またはシステムイベントからのメッセージを伝達します。
{
  "type": "message",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "channel": "slack",
    "thread_id": "C07ABCDEF-1234567890.123456",
    "sender": "user@example.com",
    "text": "Hello, can you help me?",
    "agent_name": "research-agent",
    "timestamp": "2026-03-10T10:00:00Z"
  }
}

MessagePayload フィールド

フィールド説明
channelstring送信元 Channel タイプ:"slack""line" など
thread_idstring会話のスレッド識別子
senderstring送信者識別子(メール、ユーザー ID など)
textstringメッセージ内容
agent_namestring処理対象の Agent
timestampstring (ISO 8601)メッセージ受信時刻

system

Shannon からのシステムレベル通知。
{
  "type": "system",
  "message_id": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
  "payload": {
    "text": "Agent research-agent is now available"
  }
}

claim_ack

クライアントの claim リクエストへの応答。Claim が許可されたかどうかを示します。
{
  "type": "claim_ack",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "granted": true
  }
}
フィールド説明
grantedbooleantrue の場合、このクライアントに排他的処理権が付与された

クライアントからサーバーへのメッセージ

claim

メッセージの排他的処理権を要求します。同一メッセージを Claim できるのは 1 つのクライアントのみです。
{
  "type": "claim",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

progress

Claim 済みメッセージの処理中にハートビート/進捗更新を送信します。Claim のリースが延長され、タイムアウトを防ぎます。
{
  "type": "progress",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "status": "processing",
    "percent": 50
  }
}

reply

Claim 済みメッセージの処理結果を送信します。Shannon はこれを送信元 Channel(Slack、LINE など)にルーティングします。
{
  "type": "reply",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "channel": "slack",
    "thread_id": "C07ABCDEF-1234567890.123456",
    "text": "Here is my response...",
    "format": "text"
  }
}

ReplyPayload フィールド

フィールド説明
channelstring送信先 Channel タイプ
thread_idstring返信先のスレッド
textstring応答内容
formatstring出力形式:"text" または "markdown"

disconnect

接続をグレースフルに閉じます。
{
  "type": "disconnect"
}

Claim フロー

Claim フローは分散メッセージ処理の中核プロトコルです。複数の Daemon が接続されていても、各メッセージが 1 つの Daemon のみで処理されることを保証します。
1

メッセージディスパッチ

メッセージが到着すると(Channel Webhook またはシステム経由)、Gateway は tenant:user でインデックスされたすべての対象 WebSocket 接続にメッセージをディスパッチします。
2

Claim 競争

メッセージを処理したい各 Daemon が message_id を含む claim リクエストを送信します。
3

アトミック解決

Gateway が Redis でアトミックに Claim を実行します(SETNX)。最初のクライアントが獲得し、他のクライアントは {"granted": false} を受信します。
4

メッセージ処理

獲得した Daemon がメッセージを処理します。オプションで progress メッセージを送信して Claim リースを延長し、処理状況を報告できます。
5

応答送信

Daemon が処理結果を含む reply を送信します。Shannon はこれを送信元 Channel にルーティングします。

Claim メタデータ

メッセージが Claim されると、Gateway は Redis にメタデータを保存します(TTL 60 秒):
フィールド説明
conn_idWebSocket 接続識別子
channel_id送信元 Channel ID
channel_typeChannel タイプ(slackline など)
thread_id会話スレッド ID
reply_tokenプラットフォーム固有の応答トークン(該当する場合)
timestampClaim タイムスタンプ
workflow_id関連する Temporal Workflow ID(該当する場合)
workflow_run_id関連する Temporal Workflow Run ID(該当する場合)
保留中メッセージのメタデータの TTL は 90 秒です。Claim 済みメッセージが 60 秒以内に応答されない場合、Claim は期限切れとなり、メッセージは再ディスパッチの対象となります。

Hub アーキテクチャ

WebSocket Hub はすべてのアクティブな接続を管理し、以下のルーティング戦略を採用しています:
  • Tenant-User インデックス — 接続は "tenant:user" キーでインデックスされ、ターゲットディスパッチを実現
  • スレッドスティッキールーティング — 同一スレッド("channel_type:thread_id")からのメッセージは可能な限り同じ接続にルーティング
  • Redis バックドの Claim — 分散 Claim 解決により、複数の Gateway インスタンス間の一貫性を確保

応答ルーティング

Gateway が Daemon から reply を受信すると、Claim メタデータに基づいて応答をルーティングします:
  1. Workflow 応答 — Claim メタデータに workflow_id が存在する場合、Gateway は関連する Temporal Workflow に Signal で通知します
  2. Channel 応答 — それ以外の場合、応答は送信元 Channel にルーティングされます(Slack メッセージ、LINE Push メッセージなど)

エラーハンドリング

シナリオ動作
アップグレード時の認証失敗HTTP 401 を返却、WebSocket は確立されない
メッセージが 64 KB を超過接続切断
Pong タイムアウト(60s)サーバーが接続を切断
書き込みタイムアウト(10s)メッセージ破棄、接続が切断される可能性あり
Claim 期限切れ(60s TTL)メッセージは再ディスパッチの対象
不正な JSONメッセージ無視

次のステップ