本文へ移動
hanzochang

技術と実践のノート

OpenClawの仕組み アーキテクチャ解剖 - ソースコードで読む5レイヤー設計

公開

著者:株式会社hanzochang

OpenClaw(旧Moltbot/Clawdbot)のソースコードを徹底解読。Input Sources・WebSocket Gateway・Agent Runtime・LLM Providers・Output & Storageの5レイヤー構成、ChannelPluginインターフェース、エージェントループ、スキルシステムの設計思想を解説します。

はじめに

OpenClawは、自分のデバイス上で動く「パーソナルAIアシスタント」を実現するOSSプロジェクトです。WhatsApp、Telegram、Slack、Discord、iMessage、Microsoft Teamsなど、日常的に使うメッセージングプラットフォームをすべて1つのAIが横断的に対応します。

GitHub上で30,000以上のスターを獲得し、2026年2月時点でも活発に開発が続いています。

この記事では、実際にリポジトリをcloneしてソースコードを読み込み、OpenClawがどのような設計思想でこの仕組みを実現しているのかを技術的に解説します。

対象読者
  • AIエージェントやLLMアプリケーションの設計に興味がある方
  • マルチプラットフォーム対応のメッセージングシステムを設計したい方
  • OSSの大規模TypeScriptプロジェクトのアーキテクチャを学びたい方

目次

全体アーキテクチャの概観

OpenClawは大きく分けて5つのレイヤーで構成されています。この記事では各レイヤーを順番に解説していきます。


Generated Image A2a19313 9b57 4e25 8fa5 35f75d23a79a A2a19313 9b57 4e25 8fa5 35f75d23a79a

それぞれのレイヤーが疎結合に設計されており、チャネルの追加やLLMプロバイダーの切り替えが容易になっています。

Layer 1: Input Sources - マルチチャネルシステム

最上位レイヤーは、ユーザーのメッセージが入ってくる入り口です。OpenClawの大きな特徴は、38以上のメッセージングプラットフォームを統一的に扱うプラグインシステムにあります。

チャネルプラグインアーキテクチャ

各チャネルは extensions/ ディレクトリ配下に独立したnpmパッケージとして実装されています。

text
A directory tree diagram showing the extension structure:

extensions/
├── whatsapp/
│   ├── package.json
│   ├── index.ts          (plugin entry)
│   └── src/
│       ├── channel.ts    (ChannelPlugin impl)
│       └── runtime.ts
├── telegram/
├── discord/
├── slack/
├── signal/
├── matrix/
├── bluebubbles/     (iMessage)
├── googlechat/
├── msteams/
├── irc/
├── line/
├── nostr/
└── ... (38+ channels)

Style: file tree with folder icons, syntax-highlighted, dark background
text
A directory tree diagram showing the extension structure:

extensions/
├── whatsapp/
│   ├── package.json
│   ├── index.ts          (plugin entry)
│   └── src/
│       ├── channel.ts    (ChannelPlugin impl)
│       └── runtime.ts
├── telegram/
├── discord/
├── slack/
├── signal/
├── matrix/
├── bluebubbles/     (iMessage)
├── googlechat/
├── msteams/
├── irc/
├── line/
├── nostr/
└── ... (38+ channels)

Style: file tree with folder icons, syntax-highlighted, dark background
ChannelPlugin インターフェース

すべてのチャネルは共通の ChannelPlugin インターフェースを実装します。このインターフェースは複数のアダプターで構成されています。

typescript
// ChannelPlugin<ResolvedAccount> の主要アダプター
type ChannelPlugin = {
  id: string // "whatsapp", "telegram" など
  meta: ChannelMeta // 表示名、アイコンなど

  // 設定管理:アカウント一覧、認証情報の解決
  config: ChannelConfigAdapter

  // 送信制御:テキスト分割、メディア送信、投票
  outbound: ChannelOutboundAdapter

  // セキュリティ:DMポリシー、許可リスト
  security: ChannelSecurityAdapter

  // グループ:メンション要否、ツールポリシー
  groups: ChannelGroupAdapter

  // ペアリング:デバイス認証フロー
  pairing: ChannelPairingAdapter

  // ケイパビリティ宣言
  capabilities: ChannelCapabilities
}
typescript
// ChannelPlugin<ResolvedAccount> の主要アダプター
type ChannelPlugin = {
  id: string // "whatsapp", "telegram" など
  meta: ChannelMeta // 表示名、アイコンなど

  // 設定管理:アカウント一覧、認証情報の解決
  config: ChannelConfigAdapter

  // 送信制御:テキスト分割、メディア送信、投票
  outbound: ChannelOutboundAdapter

  // セキュリティ:DMポリシー、許可リスト
  security: ChannelSecurityAdapter

  // グループ:メンション要否、ツールポリシー
  groups: ChannelGroupAdapter

  // ペアリング:デバイス認証フロー
  pairing: ChannelPairingAdapter

  // ケイパビリティ宣言
  capabilities: ChannelCapabilities
}

この設計により、各チャネルの差異(メッセージ長制限、スレッド対応の有無、メディアの扱いなど)を吸収しつつ、Gateway側は統一的にメッセージを処理できます。

各チャネルの接続方式

主要チャネルがどのように外部プラットフォームと接続しているかを整理します。

チャネル接続方式利用ライブラリ
WhatsAppQRコードによるWeb認証@whiskeysockets/baileys
TelegramBot Token + Long Pollinggrammy
SlackSocket Mode (WebSocket)@slack/bolt
DiscordBot Token + Gateway Eventsdiscord.js
Signalsignal-cli (REST wrapper)signal-cli HTTP API
iMessageBlueBubbles macOSアプリ経由REST API + Webhook
MatrixHomeserver + Access Tokenmatrix-js-sdk
Google ChatService Account + WebhookGoogle Chat API
TeamsBot FrameworkMicrosoft Bot Framework
チャネル接続方式利用ライブラリ
WhatsAppQRコードによるWeb認証@whiskeysockets/baileys
TelegramBot Token + Long Pollinggrammy
SlackSocket Mode (WebSocket)@slack/bolt
DiscordBot Token + Gateway Eventsdiscord.js
Signalsignal-cli (REST wrapper)signal-cli HTTP API
iMessageBlueBubbles macOSアプリ経由REST API + Webhook
MatrixHomeserver + Access Tokenmatrix-js-sdk
Google ChatService Account + WebhookGoogle Chat API
TeamsBot FrameworkMicrosoft Bot Framework
メッセージの正規化

各チャネルから受信したメッセージは、統一的な MsgContext 型に正規化されます。

typescript
type MsgContext = {
  Channel: string // "whatsapp", "telegram" など
  ChatType: 'direct' | 'group' | 'channel'
  From: string // 送信者/グループID
  To: string // 受信者ID
  SenderId: string // グループ内の実際の送信者
  SenderName?: string
  Text: string
  MediaUrl?: string
  ReplyToId?: string
  MessageId: string
}
typescript
type MsgContext = {
  Channel: string // "whatsapp", "telegram" など
  ChatType: 'direct' | 'group' | 'channel'
  From: string // 送信者/グループID
  To: string // 受信者ID
  SenderId: string // グループ内の実際の送信者
  SenderName?: string
  Text: string
  MediaUrl?: string
  ReplyToId?: string
  MessageId: string
}

チャネルごとにIDの形式は異なります(WhatsAppはE.164電話番号、TelegramはチャットID、Discordはユーザー/チャネルID)が、この正規化レイヤーを通すことで、上位のエージェントロジックはチャネルの差異を意識する必要がありません。

DMペアリングとセキュリティ

OpenClawでは、DMのセキュリティにペアリング方式を採用しています。


Generated Image 9fd15c27 E64d 4f08 A18c 5caeab0e0f08 9fd15c27 E64d 4f08 A18c 5caeab0e0f08 1

デフォルトではすべてのDMにペアリングが必要で、未承認のメッセージは処理されません。これはLLMへのプロンプトインジェクション攻撃に対する防御策でもあります。

Layer 2: WebSocket Gateway - 中央コントロールプレーン

Layer 1のチャネルから届いたメッセージは、すべてこのGatewayに集約されます。OpenClawの心臓部です。

Gateway Control Plane

ローカルで動作するWebSocket Gatewayが中核となり、すべてのクライアント(CLI、WebChat UI、macOSアプリ、iOS/Androidノード)を束ねます。

typescript
// Gateway はローカルの ws://127.0.0.1:18789 でリッスンする
// 接続フロー:challenge-nonce → handshake → 認証完了 → メッセージルーティング
typescript
// Gateway はローカルの ws://127.0.0.1:18789 でリッスンする
// 接続フロー:challenge-nonce → handshake → 認証完了 → メッセージルーティング

Gatewayは以下の役割を担います。

  • セッションの管理とルーティング
  • チャネル接続の制御
  • ツール実行の管理
  • イベント配信
  • 設定の永続化
接続認証の流れ

WebSocket接続には、公開鍵ベースのデバイス認証が採用されています。以下のシーケンス図で全体像を示します。


Generated Image Fc8f7765 4108 4963 A65d 2c8621627dbc Fc8f7765 4108 4963 A65d 2c8621627dbc
各ステップの解説
Step 1: チャレンジノンス

クライアントがWebSocket接続を確立した直後に、Gatewayは connect.challenge イベントを送信します。noncerandomUUID() で生成されるワンタイム値です。

typescript
const connectNonce = randomUUID()
send({
  type: 'event',
  event: 'connect.challenge',
  payload: { nonce: connectNonce, ts: Date.now() },
})
typescript
const connectNonce = randomUUID()
send({
  type: 'event',
  event: 'connect.challenge',
  payload: { nonce: connectNonce, ts: Date.now() },
})

Source: src/gateway/server/ws-connection.ts

このnonceはリプレイ攻撃を防止するために使われます。クライアントはこのnonceを含むペイロードに署名することで、「今この瞬間に接続している」ことを証明します。

Step 2: connect RPC

クライアントは署名済みのデバイス情報を含む connect RPCを送信します。最初のメッセージは必ずこの connect でなければならず、他のメソッドを送ると即座に切断されます。

ここで重要なのが scopes フィールドです。デフォルトでは空配列(=権限なし)であり、デバイスがペアリング時に明示的に承認されたスコープのみが付与されます。

typescript
// 署名対象のペイロード構築
// 各フィールドを「|」で結合した文字列に対してEd25519署名を行う
const buildDeviceAuthPayload = (params: DeviceAuthPayloadParams): string => {
  return [
    params.version,
    params.deviceId,
    params.clientId,
    params.clientMode,
    params.role,
    JSON.stringify(params.scopes ?? []),
    String(params.signedAt),
    params.token ?? '',
    params.nonce ?? '',
  ].join('|')
}

// Ed25519秘密鍵で署名し、Base64エンコードして返す
const signDeviceAuth = (payload: string, privateKey: Uint8Array): string => {
  const signature = ed25519.sign(new TextEncoder().encode(payload), privateKey)
  return Base64.encode(signature)
}
typescript
// 署名対象のペイロード構築
// 各フィールドを「|」で結合した文字列に対してEd25519署名を行う
const buildDeviceAuthPayload = (params: DeviceAuthPayloadParams): string => {
  return [
    params.version,
    params.deviceId,
    params.clientId,
    params.clientMode,
    params.role,
    JSON.stringify(params.scopes ?? []),
    String(params.signedAt),
    params.token ?? '',
    params.nonce ?? '',
  ].join('|')
}

// Ed25519秘密鍵で署名し、Base64エンコードして返す
const signDeviceAuth = (payload: string, privateKey: Uint8Array): string => {
  const signature = ed25519.sign(new TextEncoder().encode(payload), privateKey)
  return Base64.encode(signature)
}

Source: src/gateway/device-auth.ts

Step 3: 6段階の検証

Gatewayは受信した connect パラメータを以下の順序で検証します。

  1. プロトコルバージョン - minProtocolmaxProtocol がGatewayの PROTOCOL_VERSION と互換性があるか
  2. ロール - "operator" または "node" のみ許可。不正値は即切断
  3. オリジン検査 - ブラウザクライアント(WebChat、Control UI)の場合、Origin ヘッダーがホストと一致するか
  4. デバイス認証 - 公開鍵からdeviceIdを導出して一致を確認し、タイムスタンプの時刻差(10分以内)とnonceの一致をチェックした後、Ed25519署名を検証
  5. トークン認証 - 共有シークレットまたはデバイストークンで認証。失敗にはレートリミットが適用される
  6. ペアリング確認 - デバイスがペアリング済みか、ロールとスコープが承認済みかを確認。ローカル接続(loopback)は自動承認される
Step 4: hello-ok レスポンス

すべての検証に通過すると、Gatewayは hello-ok レスポンスを返します。このレスポンスには、認証されたロールとスコープ、利用可能なRPCメソッド一覧、現在のシステムスナップショットが含まれます。

typescript
// レスポンスに含まれる主要フィールド
{
  type: "hello-ok",
  protocol: PROTOCOL_VERSION,
  features: {
    methods: gatewayMethods,  // 利用可能なRPCメソッド一覧
    events: events,           // 購読可能なイベント一覧
  },
  auth: {
    deviceToken: token,       // 次回接続用のデバイストークン
    role: "operator",         // 付与されたロール
    scopes: [],               // 付与されたスコープ(default-deny)
  },
  snapshot: { presence, health, config }
}
typescript
// レスポンスに含まれる主要フィールド
{
  type: "hello-ok",
  protocol: PROTOCOL_VERSION,
  features: {
    methods: gatewayMethods,  // 利用可能なRPCメソッド一覧
    events: events,           // 購読可能なイベント一覧
  },
  auth: {
    deviceToken: token,       // 次回接続用のデバイストークン
    role: "operator",         // 付与されたロール
    scopes: [],               // 付与されたスコープ(default-deny)
  },
  snapshot: { presence, health, config }
}

deviceToken は次回接続時に使い回せるトークンで、毎回の公開鍵認証をスキップできます。ただし、ロールやスコープの変更時には再ペアリングが必要です。

認証失敗時の挙動

認証に失敗した場合、Gatewayは具体的なエラーコードを返して即座にWebSocketを閉じます。

失敗理由エラーコード対応
プロトコル不一致INVALID_REQUESTクライアント更新が必要
不正なロールINVALID_REQUEST即切断
デバイスID不一致INVALID_REQUEST鍵ペア再生成が必要
署名期限切れ(10分超過)INVALID_REQUEST時刻同期を確認
nonce不一致INVALID_REQUESTリプレイ攻撃の疑い
署名検証失敗INVALID_REQUEST秘密鍵の不一致
トークン/パスワード不一致INVALID_REQUESTレートリミット加算
未ペアリングNOT_PAIRED手動承認が必要
ハンドシェイクタイムアウト-即切断(応答なし)
失敗理由エラーコード対応
プロトコル不一致INVALID_REQUESTクライアント更新が必要
不正なロールINVALID_REQUEST即切断
デバイスID不一致INVALID_REQUEST鍵ペア再生成が必要
署名期限切れ(10分超過)INVALID_REQUEST時刻同期を確認
nonce不一致INVALID_REQUESTリプレイ攻撃の疑い
署名検証失敗INVALID_REQUEST秘密鍵の不一致
トークン/パスワード不一致INVALID_REQUESTレートリミット加算
未ペアリングNOT_PAIRED手動承認が必要
ハンドシェイクタイムアウト-即切断(応答なし)

ハンドシェイクにはタイムアウトが設定されており、一定時間内に connect RPCが届かない場合も切断されます。

RPC メソッド群

Gatewayは多数のRPCメソッドを提供します。

  • agent - エージェントの呼び出し
  • chat - チャットメッセージの処理
  • sessions - セッション管理
  • channels - チャネルステータスの取得
  • nodes - リモートノードの制御
  • cron - スケジュールジョブの操作
  • browser - ブラウザ自動化

これらはすべてWebSocketフレーム上のRequest/Response形式で通信され、冪等性を保証するためのデデュプリケーションキャッシュが実装されています。

Layer 3: Agent Runtime - エージェント実行エンジン

Gatewayがメッセージをルーティングした先がこのレイヤーです。ユーザーの意図を解釈し、ツールを使い、応答を生成するエージェントの本体にあたります。

エージェントループの全体像

ユーザーからのメッセージが処理される流れを追います。


Generated Image 9453c043 F523 4ff2 B295 71f8aba9b494 9453c043 F523 4ff2 B295 71f8aba9b494
二段構成のランタイム

エージェントランタイムは外部ループと内部ループの二段構成になっています。

外部ループ(runEmbeddedPiAgent)

外部ループは以下を担当します。

  • 認証プロファイルの管理とフォールオーバー
  • コンテキストウィンドウのオーバーフロー検出
  • 自動コンパクション(会話履歴の圧縮)
  • 使用量の累積追跡
  • リトライロジック
内部ループ(runEmbeddedAttempt)

内部ループは実際のLLM呼び出しを行います。

  • @mariozechner/pi-agent-core によるセッション管理
  • ツールのセットアップと登録
  • ストリーミングレスポンスの処理
  • アボートシグナルとタイムアウトの管理

この二段構成により、LLMのAPI失敗やコンテキストオーバーフローが発生しても、上位でリカバリーできる設計になっています。

ツールシステム

OpenClawには50以上のツールが組み込まれています。

ツールの分類
  • read, write, edit - ファイル操作
  • exec, bash - コマンド実行
  • browser - Playwright によるブラウザ自動化
  • message - チャネルへのメッセージ送信
  • discord-actions, slack-actions - チャネル固有API操作
  • cron - スケジュール実行
  • canvas - A2UI レンダリング
  • web-search - Web検索
ツールポリシーによる制御

ツールの利用はきめ細かいポリシーで制御されます。

typescript
// ツールポリシーの適用階層(上が優先)
// 1. グループ固有のポリシー
// 2. エージェント固有のポリシー
// 3. グローバルポリシー

// 例:Slackの特定チャネルではブラウザツールを禁止
{
  channels: {
    slack: {
      channels: {
        "C0123ABC": {
          tools: "allowlist",
          toolsAllowFrom: ["U_OWNER_ID"]
        }
      }
    }
  }
}
typescript
// ツールポリシーの適用階層(上が優先)
// 1. グループ固有のポリシー
// 2. エージェント固有のポリシー
// 3. グローバルポリシー

// 例:Slackの特定チャネルではブラウザツールを禁止
{
  channels: {
    slack: {
      channels: {
        "C0123ABC": {
          tools: "allowlist",
          toolsAllowFrom: ["U_OWNER_ID"]
        }
      }
    }
  }
}

Source: src/agents/pi-tools.policy.ts

ツールはデフォルトで無効(default-deny)であり、明示的に許可されたスコープ内でのみ実行できます。

スキルシステム

スキルは、エージェントの機能を拡張する自己完結型のモジュールです。各スキルは SKILL.md というフロントマター付きMarkdownで定義されます。

yaml
---
name: github
description: Interact with GitHub repositories
metadata:
  openclaw:
    emoji: "\U0001F4BB"
    requires:
      anyBins: ['gh']
      env: ['GITHUB_TOKEN']
---
yaml
---
name: github
description: Interact with GitHub repositories
metadata:
  openclaw:
    emoji: "\U0001F4BB"
    requires:
      anyBins: ['gh']
      env: ['GITHUB_TOKEN']
---
スキルの読み込み優先順位

スキルは複数のソースから読み込まれ、後のものが前のものを上書きします。


Generated Image 74aa7298 A04f 4374 9395 5a4de639aafd 74aa7298 A04f 4374 9395 5a4de639aafd

53個のバンドルスキルには、GitHub操作、Apple Notes連携、Discord操作、PDF処理、コード補完など多岐にわたるものが含まれています。

スキルのライフサイクル
  1. 発見 - 各ソースディレクトリからスキルを検出
  2. フィルタリング - プラットフォーム要件(macOS限定など)や設定による有効/無効の判定
  3. プロンプト注入 - 有効なスキルの説明文をLLMのシステムプロンプトに挿入
  4. 実行 - LLMがスキルを呼び出すと、SDK経由でスクリプトが実行される
  5. 結果返却 - stdout/stderrがキャプチャされ、ツール結果としてLLMに返る

Layer 4: LLM Providers - モデル抽象化レイヤー

Agent Runtimeがプロンプトを組み立てた後、実際のLLM呼び出しを行うのがこのレイヤーです。複数のプロバイダーを透過的に切り替える仕組みが実装されています。

プロバイダーの抽象化

OpenClawは以下のLLMプロバイダーに対応しています。

  • Anthropic - Claude(推奨、プライマリ)
  • OpenAI - GPTモデル
  • Google - Gemini
  • Ollama - ローカルモデル
  • GitHub Copilot
  • AWS Bedrock
typescript
// プロバイダー解決の流れ
// 1. 認証プロファイルの順序を解決
const profileOrder = resolveAuthProfileOrder({
  cfg: config,
  store: authStore,
  provider,
  preferredProfile: preferredProfileId,
})

// 2. モデルの解決
const { model, authStorage, modelRegistry } = resolveModel(provider, modelId, agentDir, config)

// 3. プロバイダー固有のストリーム関数を設定
// Ollama(ローカルモデル)の場合はカスタムストリーム
if (model.api === 'ollama') {
  session.agent.streamFn = createOllamaStreamFn(ollamaBaseUrl)
} else {
  session.agent.streamFn = streamSimple // SDK標準
}
typescript
// プロバイダー解決の流れ
// 1. 認証プロファイルの順序を解決
const profileOrder = resolveAuthProfileOrder({
  cfg: config,
  store: authStore,
  provider,
  preferredProfile: preferredProfileId,
})

// 2. モデルの解決
const { model, authStorage, modelRegistry } = resolveModel(provider, modelId, agentDir, config)

// 3. プロバイダー固有のストリーム関数を設定
// Ollama(ローカルモデル)の場合はカスタムストリーム
if (model.api === 'ollama') {
  session.agent.streamFn = createOllamaStreamFn(ollamaBaseUrl)
} else {
  session.agent.streamFn = streamSimple // SDK標準
}

Source: src/agents/embedded-agent.ts

認証とフォールオーバー

認証情報は環境変数、macOSのKeychain、AWSの認証情報、設定ファイルなど複数のソースから解決されます。

あるプロバイダーがレートリミットに引っかかった場合、自動的に次の認証プロファイルにフォールオーバーする仕組みになっています。失敗したプロファイルはクールダウン期間が設定され、一時的にスキップされます。

Layer 5: Output & Storage - 出力と永続化

LLMの応答が生成された後、それをユーザーに届け、状態を永続化するのがこのレイヤーです。セッション管理、メモリ、音声合成、Canvas/A2UIなど多くの出力系サブシステムが含まれます。

セッション管理

OpenClawのセッションは以下のように分類されます。

  • main - デフォルトの1対1会話
  • group - グループチャット(グループごとに分離)
  • thread - スレッド会話(プラットフォームがサポートする場合)

セッションキーは agentId + accountId + peerId + channel の組み合わせで一意に決定されます。

会話履歴はSQLiteデータベースに永続化され、ブランチ(会話の分岐)もサポートされています。

メモリ/RAGシステム

OpenClawはベクトル検索ベースの長期記憶システムを備えています。

typescript
// メモリエントリの構造
type MemoryEntry = {
  id: string
  text: string
  vector: number[] // 埋め込みベクトル
  importance: number // 重要度スコア
  category: MemoryCategory // facts, preferences, context など
  createdAt: number
}
typescript
// メモリエントリの構造
type MemoryEntry = {
  id: string
  text: string
  vector: number[] // 埋め込みベクトル
  importance: number // 重要度スコア
  category: MemoryCategory // facts, preferences, context など
  createdAt: number
}
  • 埋め込みにはOpenAIのtext-embedding-3モデルを使用
  • ベクトルストレージにはLanceDB(SQLite-vec)を採用
  • エージェント実行前にセマンティック検索が行われ、関連する記憶がコンテキストに注入されます
Cron/Webhookシステム

スケジュール実行

Cronジョブは、定期的にエージェントを起動して処理を実行する仕組みです。

typescript
// Cronジョブの定義例
{
  payload: {
    kind: "agentTurn",
    prompt: "今日のニュースをまとめて"
  },
  schedule: {
    cron: "0 9 * * MON-FRI",  // 平日9時
    timezone: "Asia/Tokyo"
  },
  delivery: {
    channel: "telegram",
    to: "123456789"
  }
}
typescript
// Cronジョブの定義例
{
  payload: {
    kind: "agentTurn",
    prompt: "今日のニュースをまとめて"
  },
  schedule: {
    cron: "0 9 * * MON-FRI",  // 平日9時
    timezone: "Asia/Tokyo"
  },
  delivery: {
    channel: "telegram",
    to: "123456789"
  }
}

サブエージェントの生成、結果のベストエフォート配信、セッションの自動クリーンアップなどが実装されています。

Webhook

HTTPエンドポイントとして以下が提供されます。

  • POST /hooks/agent - エージェント呼び出し
  • POST /hooks/wake - テキスト入力によるウェイク
  • POST /hooks/schedule - Cronジョブの作成/更新
  • POST /slack, POST /openai - 各プラットフォーム固有のWebhook

Webhookにはトークン認証とレートリミットが適用されます。

Canvas / A2UI

A2UI(Artifact to UI)は、エージェントがリアルタイムにUIをレンダリングするためのプロトコルです。

typescript
// Canvas ツールのアクション
const CANVAS_ACTIONS = [
  'present', // Canvasを表示
  'hide', // Canvasを非表示
  'navigate', // URLを変更
  'eval', // JavaScriptを実行
  'snapshot', // スクリーンショットを取得
  'a2ui_push', // A2UIの更新をプッシュ
  'a2ui_reset', // A2UIの状態をリセット
]
typescript
// Canvas ツールのアクション
const CANVAS_ACTIONS = [
  'present', // Canvasを表示
  'hide', // Canvasを非表示
  'navigate', // URLを変更
  'eval', // JavaScriptを実行
  'snapshot', // スクリーンショットを取得
  'a2ui_push', // A2UIの更新をプッシュ
  'a2ui_reset', // A2UIの状態をリセット
]

Source: src/agents/tools/canvas.ts

A2UIのコンポーネントはReactで実装され、UMD形式にバンドルされてサンドボックス内のiframeで実行されます。

ネイティブアプリによる出力

OpenClawはCLIだけでなく、ネイティブアプリも出力先として提供しています。

  • macOS - SwiftUI製メニューバーアプリ
  • iOS - SwiftUI製コンパニオンアプリ
  • Android - Kotlin製アプリ

いずれもGatewayへWebSocket接続し、Voice Wake(音声起動)やTalk Mode(音声対話)、Canvasレンダリングに対応しています。

横断的な仕組み

5つのレイヤーを横断する仕組みについても触れておきます。

設定システム

OpenClawの設定は ~/.openclaw/openclaw.json5 で管理されます。JSON5形式が採用されており、コメントを記述できます。

json5
{
  // Gateway設定
  gateway: {
    port: 18789,
  },
  // チャネル設定(Layer 1)
  channels: {
    telegram: {
      enabled: true,
      accounts: {
        default: { token: 'BOT_TOKEN' },
      },
    },
  },
  // エージェント設定(Layer 3-4)
  agents: {
    default: {
      model: 'anthropic/claude-opus-4-6',
    },
  },
  // マルチエージェントルーティング(Layer 2)
  bindings: [
    { agentId: 'default', match: { channel: 'telegram' } },
    { agentId: 'work-agent', match: { channel: 'slack' } },
  ],
}
json5
{
  // Gateway設定
  gateway: {
    port: 18789,
  },
  // チャネル設定(Layer 1)
  channels: {
    telegram: {
      enabled: true,
      accounts: {
        default: { token: 'BOT_TOKEN' },
      },
    },
  },
  // エージェント設定(Layer 3-4)
  agents: {
    default: {
      model: 'anthropic/claude-opus-4-6',
    },
  },
  // マルチエージェントルーティング(Layer 2)
  bindings: [
    { agentId: 'default', match: { channel: 'telegram' } },
    { agentId: 'work-agent', match: { channel: 'slack' } },
  ],
}

設定スキーマはZodで定義されており、100以上の型が存在します。1つの設定ファイルで全レイヤーを横断的に制御できる点が特徴です。

ビルドシステム

OpenClawはpnpm workspacesによるモノレポ構成です。

text
A monorepo structure diagram:

openclaw/ (root)
├── src/              → Core TypeScript (tsdown でバンドル)
├── ui/               → Web UI (Lit + Vite)
├── apps/
│   ├── macos/        → Swift + SwiftUI
│   ├── ios/          → Swift + SwiftUI
│   └── android/      → Kotlin + Gradle
├── packages/
│   ├── clawdbot/     → 後方互換シム
│   └── moltbot/      → 後方互換シム
├── extensions/       → 38+ チャネルプラグイン (各npm pkg)
└── skills/           → 53+ バンドルスキル

Style: tree diagram with package icons, color-coded by type, dark background
text
A monorepo structure diagram:

openclaw/ (root)
├── src/              → Core TypeScript (tsdown でバンドル)
├── ui/               → Web UI (Lit + Vite)
├── apps/
│   ├── macos/        → Swift + SwiftUI
│   ├── ios/          → Swift + SwiftUI
│   └── android/      → Kotlin + Gradle
├── packages/
│   ├── clawdbot/     → 後方互換シム
│   └── moltbot/      → 後方互換シム
├── extensions/       → 38+ チャネルプラグイン (各npm pkg)
└── skills/           → 53+ バンドルスキル

Style: tree diagram with package icons, color-coded by type, dark background

バージョニングにはカレンダーバージョニング(v2026.2.15のような日付ベース)が採用されています。

デフォルト拒否のセキュリティモデル

すべてのレイヤーを貫くセキュリティ思想が「デフォルト拒否」です。

  • Layer 1 - DMはペアリング必須、グループは許可リスト制
  • Layer 2 - Gateway接続は公開鍵認証、認証失敗にはレートリミット
  • Layer 3 - ツールスコープはデフォルトで空、チャネル/グループ単位のポリシー
  • Layer 4 - 認証プロファイルの分離、クールダウンによる保護
  • Layer 5 - セッション分離、Webhookのトークン認証

LLMを外部メッセージングに接続するという性質上、プロンプトインジェクション攻撃への耐性が求められるため、この徹底した設計は理にかなっています。

プラグインによる拡張性

38以上のチャネルが存在しますが、コアのGatewayコードはチャネルの詳細を知りません。すべて ChannelPlugin インターフェースを通じてやり取りされるため、新しいチャネルの追加はGatewayの変更なしに行えます。

typescript
// プラグインの最小実装
const myPlugin = {
  id: 'my-channel',
  name: 'My Channel',
  register(api) {
    api.registerChannel({ plugin: myChannelPlugin })
  },
}
export default myPlugin
typescript
// プラグインの最小実装
const myPlugin = {
  id: 'my-channel',
  name: 'My Channel',
  register(api) {
    api.registerChannel({ plugin: myChannelPlugin })
  },
}
export default myPlugin

まとめ

OpenClawのアーキテクチャを5つのレイヤーに沿って調査した結果、以下のことが分かりました。

  • Layer 1(Input Sources) - チャネルプラグインシステムにより38以上のメッセージングプラットフォームを統一的に扱い、MsgContext への正規化で差異を吸収している
  • Layer 2(Gateway) - WebSocket Gatewayが中央のコントロールプレーンとして機能し、すべてのクライアント・チャネル・エージェントを統合している
  • Layer 3(Agent Runtime) - 二段構成のランタイムでLLMの障害やコンテキストオーバーフローからのリカバリーが考慮されている。ツールとスキルにより行動範囲を拡張できる
  • Layer 4(LLM Providers) - 複数プロバイダーへの対応と自動フォールオーバーにより、特定のLLMに依存しない設計になっている
  • Layer 5(Output & Storage) - SQLiteによるセッション永続化、ベクトル検索ベースのRAG、Canvas/A2UI、ネイティブアプリなど多彩な出力先を提供している

「パーソナルAIアシスタント」をOSSで実現するにあたって、チャネル統合・セキュリティ・拡張性のバランスをどう取るかという点で、参考になる設計判断が数多く含まれています。

参考リンク

この記事が参考になったら

この記事が役に立ったと思いましたら、よろしければフォローをお願いします。

Xでフォローする

OpenClawの導入やAIエージェントのアーキテクチャ設計に興味がありましたら、お気軽にご連絡ください。

お問い合わせ

この記事を書いた人

Webプランナー、開発会社の創業メンバーを経て、Fintech・DX・Web3の企画・開発に従事。2021年に独立し、2023年に株式会社hanzochangを設立。現在はAIを活用した業務改善と、自社サービス「指定管理者制度AI」の開発・運用に取り組んでいます。

半澤勇大のXアカウント(新しいタブで開く)

この記事の株式会社hanzochangについて
少し紹介させてください!

この記事に関連する当社のサービス

AIエージェント開発

人の代わりに仕事を進めるAIを、御社の業務にも。

情報収集、原稿や画像の作成、システムへの入力など、複数の作業を実行するAIエージェントを開発します。人が確認する工程も含めて設計し、本番運用まで支援します。

サービスの詳細を見る
メガネをかけたJくんが座禅を組み、背後に多数の手描きワークフローが広がるイラスト。
  • 担当する仕事を設計

    AIに任せる作業と、人が判断する条件を整理。

  • 資料・システムと連携

    社内データやAPIにつなぎ、一連の作業を実装。

  • 評価・本番運用

    出力の精度と実行結果を確かめ、継続的に改善。

業務改善・AI活用について、お気軽にご相談ください。