Managed AgentsをChat SDK/assistant-ui/CopilotKitと組み合わせる実装ガイド
Managed Agentsを人気のチャットフレームワーク3種と組み合わせて動くアプリにする公式クイックスタートを比較します。セッションとフロントエンドの役割分担、承認ゲートが現れる条件、再接続とトークン表示の注意点を扱います。
Managed Agentsは単体でAPIを叩くだけでも動きます。公式クイックスタートは、VercelのChat SDK・assistant-ui・CopilotKit(AG-UI)という3つのチャットフレームワークとペアにして、動くアプリとして配布しています。どの組み合わせも、フレームワークがチャット画面を描画し、マネージドセッションがサーバー側でエージェントループを回す点は同じです。違いは、セッションのイベントを画面にどう写すかと、ツールの承認をどこで挟むかにあります。
3つのクイックスタートに共通する役割分担
責務は次のように分かれます。
- セッション(サーバー側): イベント履歴を保持し、サンドボックスでツールを実行し、進捗をイベントとしてストリーミングする
- フロントエンド(チャットフレームワーク側): セッションが流すイベントを受け取り、メッセージ・ツール呼び出しカード・チャートなどの画面部品に変換して描画する
この分担は、Managed Agentsの設計思想のハーネスとサンドボックスの分離と同じ考え方です。セッションが実行状態を丸ごと持つので、フロントエンド側は「イベントをどう見せるか」に専念できます。
通信はイベントベースです。ユーザーの発言はuser.messageイベントで送り、進捗やツール呼び出しはagent.*イベントとしてサーバー送信イベント(SSE)で返ってきます。イベント履歴はサーバー側に残るので、フロントエンドは再接続しても経緯を取得し直せます。フレームワーク側が書くのは、このストリームを購読して種類ごとに画面部品へ対応づけるアダプターです。
3つの実装を比較する
公式クイックスタートが示す3つの実装は、題材も画面の作り方も違います。
| フレームワーク | 想定アプリ | セッションとの対応 | 特徴的な実装ポイント |
|---|---|---|---|
| Chat SDK(Vercel) | 想定アプリブラウザーチャットのリサーチアナリスト | セッションとの対応会話1件が永続セッション1件に対応し、返信をストリーミングしながらツール呼び出しをライブフィードで表示する | 特徴的な実装ポイントアダプターを差し替えるだけで、同じハンドラーをSlack・Teams・Discord・WhatsAppに移せる |
| assistant-ui | 想定アプリスプレッドシート分析チャット | セッションとの対応セッション一覧がそのままスレッドリストになり、1つのreducerがイベントログをメッセージとツールカードに変換する | 特徴的な実装ポイントBashコマンドごとに、実行前のインラインAllow/Denyゲートを描画する |
| CopilotKit(AG-UI) | 想定アプリ家計アシスタント | セッションとの対応チャットスレッド1件がマネージドセッション1件に対応する | 特徴的な実装ポイントAG-UIアダプターが返信をトークン単位でストリーミングし、カスタムツールがチャートを会話内に描画する |
3つとも「セッション = 会話の単位」という対応は共通ですが、ツール呼び出しの見せ方が分かれます。ファイル削除やコード実行のような操作を扱うアプリなら、assistant-uiの承認ゲートは参考にしやすい実装です。
承認ゲートは確認が要る呼び出しにだけ現れる
assistant-uiのクイックスタートは、Bashコマンドごとに実行前のAllow/Denyゲートを描画します。APIには、これに対応するtool confirmation(承認要求への応答)があります。見落としやすいのは、ゲートが出る条件です。権限ポリシーにはalways_allowとalways_askがあります。エージェントツールセット(Bashなど)の既定はalways_allowで、MCPツールセットの既定はalways_askです。
つまり既定のままBashを渡したエージェントでは、確認は一度も発生しません。Bashだけに承認を求めたいなら、エージェントを作る時点でdefault_configはalways_allowのままにして、configsのbash用エントリーにalways_askを指定します。default_configにalways_askを指定すると、エージェントツールセット全体が承認待ちになります。各agent.tool_useとagent.mcp_tool_useイベントにはevaluated_permissionが付きます。値はallow / ask / denyのいずれかで、待機するのはaskのイベントだけです。ゲートを自前で作るフロントエンドは、このフィールドで「確認が要る呼び出し」を判別できます。多くのイベントにはevaluationオブジェクトも付き、どのポリシーがその結果を出したかが記録されます。たとえばalways_askの下で止まったBash呼び出しなら、evaluationのtypeはalways_askです。
tools = [{
"type": "agent_toolset_20260401",
"default_config": {"permission_policy": {"type": "always_allow"}},
"configs": [
{"name": "bash", "permission_policy": {"type": "always_ask"}},
],
}]もう1つの選択肢がautoポリシーで、サーバーが呼び出しごとに評価します。安全と判断された呼び出しはそのまま実行され、高リスクと判断された呼び出しは拒否されます。拒否はクライアント側から覆せません。判断がつかないときだけ、always_askと同じく一時停止します。どのツールセットも、既定ではautoになりません。
承認が要る呼び出しの流れ
- 1
ツール呼び出しが通知される
セッションが
agent.tool_useまたはagent.mcp_tool_useイベントを発行します。 - 2
セッションが一時停止する
stop_reason: requires_actionを含むsession.status_idleイベントが届きます。止めているイベントのIDはstop_reason.event_idsの配列に入っています。 - 3
許可か拒否を返す
各IDに対して
user.tool_confirmationイベントを送ります。resultはallowかdenyで、拒否にはdeny_messageで理由を添えられます。 - 4
セッションが再開する
止めていたイベントがすべて解決すると、セッションは
runningに戻ります。
with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop_reason := event.stop_reason):
if stop_reason.type == "requires_action":
for event_id in stop_reason.event_ids:
client.beta.sessions.events.send(
session.id,
events=[{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
}],
)このコードは承認を自動で通すだけの最小形です。実際のフロントエンドでは、ここでallowを即座に返さず、画面にボタンを出してユーザーの選択を待ちます。この契約はAPI共通で、フレームワークごとの独自仕様ではありません。フレームワークが違っても契約は同じなので、自前でゲートを作るなら、requires_actionを監視してuser.tool_confirmationを送り返す処理を足す形になります。承認待ちのセッションは、応答が届くまで無期限に待ちます。evaluated_permissionがaskでないイベントにuser.tool_confirmationを送ると、APIは400エラーで拒否します。承認待ちの状態はセッションの側にあるので、セッションを再開可能な単位として扱う設計とも整合します。人とAIが共同で確認すると正解率が上がる効果は、Claudeとの共同作業で正解率が上がる理由で報告されています。
サンドボックスのツールとカスタムツールを分けて考える
もう1つの観点は、ツールがどこで実行されるかです。
ツール呼び出しの2系統
サンドボックス実行のツール
agent.tool_useで通知され、サンドボックス内で完結して結果が返ります。フロントエンドは進捗を表示するだけで、実行には関与しません。
カスタムツール
agent.custom_tool_useで通知され、セッションはrequires_actionで一時停止します。実行するのはアプリ側のコードで、結果をuser.custom_tool_resultで返すまで再開しません。
カスタムツールの結果は、止まっているイベントIDをcustom_tool_use_idに入れて送ります。承認の返信がtool_use_idなのと名前が違うので、両方を扱うアダプターでは取り違えやすい箇所です。
CopilotKit(AG-UI)の実装が「カスタムツールがチャートを会話内に描画する」形を取れるのは、この2系統目の仕組みです。チャートの描画はサンドボックスの外、つまりアプリ側で起きます。サンドボックス内で完結する処理(コード実行・ファイル生成)は前者に任せ、アプリのUIそのものを動かす処理(チャート、フォーム、通知)は後者に任せる切り分けが自然です。assistant-uiのAllow/Denyゲートは前者への確認、CopilotKitのチャートは後者の実行そのものという違いがあります。
既存のUIコンポーネントが豊富なチームなら、描画をカスタムツールに寄せるとサンドボックス側の実装を増やさずに済みます。自社UIがまだないなら、サンドボックス実行のツールだけで動かし、画面が固まってからカスタムツールを足す順序のほうが、スキーマの変更が両側に及びにくくなります。
再接続とトークン表示で落とし穴になる点
チャットUIはタブを閉じる、回線が切れるといった中断を前提にしなければなりません。公式のイベントストリーム解説は、取りこぼしなく再接続する手順を3段階で示しています。新しいストリームを開き、イベント履歴を全件取得して既出IDの集合を作り、ライブのストリームでは既出のイベントを読み飛ばします。履歴を取り直せるのは、前述のとおりイベントがサーバー側に残るからです。
トークン単位の表示は、既定では有効になりません。エージェントの返答は、既定では1回のモデルリクエストが終わった後に、まとまったagent.messageとして届きます。逐次表示したい接続は、ストリームを開くときにevent_deltas[]クエリパラメーターでagent.messageを指定します。指定は接続ごとなので、フロントエンドが再接続したら指定し直します。
プレビューには制約が4つあり、どれもUIの作りに響きます。
- 負荷によってはデルタが間引かれ、途中までの文字列で止まることがある。確定した内容は後から届く
agent.messageが正で、プレビューを最終結果として扱わない - 再接続した接続には、すでに始まったリクエストのデルタは届かない。取りこぼした分を再取得する方法はなく、履歴の
agent.messageで補う - プレビューの対象は、読んでいるスレッドのアシスタントのテキストだけ。ツール呼び出しやツール結果は含まれない
event_startとevent_deltaはイベント履歴に残らない。ライブ配信専用のイベントである
返信の逐次表示にはデルタ、ツール呼び出しの表示には通常のイベントを使う、と分けて考えると、3つのクイックスタートのコードを読みやすくなります。
どのフレームワークを選ぶか
3つは技術的な優劣ではなく、作りたいアプリの性格に応じた選択肢です。
| こう作りたい | 向くフレームワーク |
|---|---|
| 汎用チャットとして作り、後でSlackやDiscordにも配りたい | 向くフレームワークChat SDK |
| ツール実行の可否をユーザーに確認させたい業務系アプリ | 向くフレームワークassistant-ui |
| 既存のCopilotKit UIやAG-UI準拠のフロントエンドに載せたい | 向くフレームワークCopilotKit(AG-UI) |
3つともGitHubでソース一式が公開されているので、まず動かしてイベントの流れを目で追い、自社のUIコンポーネントに置き換えていく進め方が近道です。Claude Coworkのように完成したUIを提供する製品とは違い、UIは開発者が持ち込む前提です。セッション管理はAnthropic側に任せ、画面は自社ブランドで作りたいという要件に向く構成です。
どのクイックスタートも、最小構成の実装例として読むのが現実的です。そこから自社の基盤に載せるときは、承認ポリシーの設定(上記の既定値)、再接続の処理、トークン表示の扱いを自分で足します。セッションに支出上限(budget)を付けた場合、上限に達したセッションは終了せず、stop_reasonがbudget_reachedのアイドル状態で止まります。この間に受け付けるのは、処理中の作業を片づけるイベントだけです。具体的にはuser.tool_confirmation・user.tool_result・user.custom_tool_result・user.interruptが通ります。user.messageのように新しい作業を始めるイベントは400エラーで拒否されます。入力欄が無反応に見えないよう、上限到達を画面に出す処理もフロントエンド側に要ります。
まとめ
フレームワークは、配布先のメッセージング面と承認UIの要否で選びます。権限ポリシーはその選択とは独立に決まるため、承認ゲートが要るなら、先にツールセットがalways_askになっているかを見るのが近道です。