Claude Media
Managed Agentsへの移行 — Agent SDKとMessages APIから

Managed Agentsへの移行 — Agent SDKとMessages APIから

Messages APIの手書きループやClaude Agent SDKからClaude Managed Agentsへ移行する際の設定対応表とコード差分、移行チェックリストを解説します。

Claude Managed Agentsへの移行で変わるのは「誰がループを回すか」です。Messages APIの手書きループなら会話履歴の管理・ツール実行・完了判定がまるごとサーバー側に移り、Claude Agent SDKからならプロセス内で動いていたものがAnthropicのインフラ上で動くようになります。どちらの移行元でも、システムプロンプトとツール定義自体はほぼそのまま持ち込めます。

Messages APIの手書きループから移行するときに消えるコード

messages.createwhileループで呼び、tool_useブロックを自分で処理し、tool_resultを会話履歴に積み戻す実装をしていた場合、そのコードの大部分が不要になります。

Before(手書きループ)After(Managed Agents)
会話履歴の配列を自分で保持し、毎ターン渡すAfter(Managed Agents)セッションがサーバー側で履歴を保持。イベントを送受信するだけ
tool_useブロックを1つずつ処理し、tool_resultでループに戻すAfter(Managed Agents)ビルトインツールはサンドボックス内で自動実行。カスタムツールだけagent.custom_tool_useイベントで受け取る
実行用のサンドボックスを自前で用意するAfter(Managed Agents)セッションのサンドボックスがコード実行・ファイル操作・bashを一括して担う
ループの終了判定を自分で書くAfter(Managed Agents)エージェントがやることを終えるとsession.status_idleが送られてくる

Before / Afterのコード量の差は歴然です。Beforeはwhile Trueの中でstop_reasonを見てtool_useブロックを走査し、execute_toolを呼んで結果を会話履歴に積み戻す、という定型処理を自分のプロセス内に書き続けます。Afterでは、エージェントとセッションを1回作成したあと、SSEストリームを張ってuser.messageを送り、session.status_idleが来るまで待つだけです。

# Before: 会話履歴とツール実行ループを自分で回す
messages = [{"role": "user", "content": task}]
while True:
    response = client.messages.create(
        model="claude-opus-5", max_tokens=1024, messages=messages, tools=tools,
    )
    messages.append({"role": "assistant", "content": response.content})
    if response.stop_reason == "end_turn":
        break
    for block in response.content:
        if block.type == "tool_use":
            result = execute_tool(block.name, block.input)
            messages.append({"role": "user", "content": [
                {"type": "tool_result", "tool_use_id": block.id, "content": result}
            ]})
# After: エージェントとセッションを作り、イベントを送受信するだけ
agent = client.beta.agents.create(
    name="Task Runner", model="claude-opus-5",
    tools=[{"type": "agent_toolset_20260401"}],
)
session = client.beta.sessions.create(
    agent={"type": "agent", "id": agent.id, "version": agent.version},
    environment_id=environment.id,
)
with client.beta.sessions.events.stream(session.id) as stream:
    client.beta.sessions.events.send(
        session.id,
        events=[{"type": "user.message", "content": [{"type": "text", "text": task}]}],
    )
    for event in stream:
        if event.type == "session.status_idle":
            break

システムプロンプト・モデル・Web検索やWeb取得のallowed_domainsblocked_domainsmax_content_tokensuser_locationは同じフィールド名のまま、エージェント定義のweb_searchweb_fetch設定に移ります。ただしmax_usescitationscache_controlはManaged Agentsには存在しません。カスタムツールはJSON Schemaでの定義自体は変わらず、実行だけがインラインの分岐処理からagent.custom_tool_useイベントへの応答に置き換わります。

CLIでエージェントをファイル管理する選択肢

curlやSDKで毎回エージェントを作成する代わりに、ant applyコマンドでMarkdownファイルからエージェントを定義する方法もあります。CLAUDE.mdのようにエージェント定義をリポジトリで管理し、レビューを経てから適用したいチームに向いています。

---
name: Task Runner
model: claude-opus-5
tools:
  - type: agent_toolset_20260401
---
ant apply agent.md

このファイルをGitで管理すれば、システムプロンプトやツール構成の変更履歴がそのままエージェントの変更履歴になります。手書きループのコードにシステムプロンプトを文字列としてハードコードしていた場合、この形式へ移すだけでもレビューのしやすさが変わります。

Claude Agent SDKから移行するときの設定対応

すでにAgent SDKでエージェント・ツール・セッションという概念に慣れている場合、移行の本質は「どこで実行されるか」の違いです。SDKは自分が操作するプロセス内で動きますが、Managed AgentsはAnthropicのインフラ上で動きます。設定オブジェクトのAPI側の等価物へのマッピングが移行作業の大半を占めます。

Agent SDKManaged Agents
実行のたびに構築するClaudeAgentOptions(...)Managed Agentsclient.beta.agents.create(...)を1回。エージェントはサーバー側で永続化・バージョン管理される
ClaudeSDKClient(...)query(...)Managed Agentsclient.beta.sessions.create(...)のあと、イベントの送受信
@toolデコレーターでSDKが自動ディスパッチManaged Agentsエージェント側で{"type": "custom", ...}として宣言し、クライアントがagent.custom_tool_useイベントを処理してuser.custom_tool_resultで返す
ビルトインツールが自分のプロセス・ファイルシステムに対して動くManaged Agents{"type": "agent_toolset_20260401"}がセッションのサンドボックス内で/workspaceに対して動く
cwdadd_dirsでローカルパスを指定Managed AgentsFiles APIでファイルをアップロードし、resourcesにファイルIDとマウント先パスの組を並べてセッションへ渡す
system_promptCLAUDE.md階層Managed Agentsエージェントのsystem文字列1つ。更新のたびに新しいバージョンがサーバー側にでき、セッションを特定バージョンへ固定できる
1箇所で設定・認証するmcp_serversManaged Agentsエージェントにサーバーを宣言し、セッションのvaultで認証情報を渡す。詳細はManaged AgentsのMCP接続を参照
permission_modecan_use_toolManaged Agentsツールごとのpermission_policyalways_askのツールにはuser.tool_confirmationイベントを送る

エージェントと環境は一度作れば、複数のセッションで使い回します。カスタムツールの関数自体は引き続き自分のプロセス内で動きますが、SDKが自動的にディスパッチしていた部分を、agent.custom_tool_useイベントを読んで結果を明示的に送り返す処理として自分で書く必要があります。

カスタムツールの定義そのものはほぼ書き直さずに済む

Agent SDKで@toolデコレーターを使い、create_sdk_mcp_serverでMCPサーバーとして登録していたカスタムツールがある場合、ツールの中身(関数のロジックと入力スキーマ)は書き直しません。変わるのは登録の形式と、呼び出しを受け取る場所だけです。

# Before: Agent SDKの@toolデコレーターとSDK内蔵MCPサーバー
@tool("get_weather", "Get the current weather for a city.", {"city": str})
async def get_weather(args: dict) -> dict:
    return {"content": [{"type": "text", "text": f"{args['city']}: 18°C, clear"}]}
 
options = ClaudeAgentOptions(
    model="claude-opus-5",
    system_prompt="You are a concise weather assistant.",
    mcp_servers={"weather": create_sdk_mcp_server("weather", "1.0", tools=[get_weather])},
)
# After: エージェント定義にcustomツールとして宣言し、イベントで応答する
agent = client.beta.agents.create(
    name="weather-agent",
    model="claude-opus-5",
    system="You are a concise weather assistant.",
    tools=[{
        "type": "custom",
        "name": "get_weather",
        "description": "Get the current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    }],
)
 
def get_weather(city: str) -> str:
    return f"{city}: 18°C, clear"
 
# イベントループの中で agent.custom_tool_use を受け取ったら
# get_weather を呼び、user.custom_tool_result で結果を送り返す

入力スキーマの書式はJSON Schemaのまま変わりません。違うのは、SDKがcreate_sdk_mcp_server経由で自動的に呼び出しを仲介していたのに対し、Managed Agentsではagent.custom_tool_useイベントを自分のイベントループで拾い、user.custom_tool_resultイベントとしてcustom_tool_use_id付きで送り返す必要がある点です。ツールが増えるほど、この分岐処理はイベントのtypeで振り分ける単純なディスパッチテーブルにまとめておくと見通しが良くなります。

もう一つ新しく必要になる概念が「環境」です。Agent SDKはローカルプロセスのcwdやネットワークをそのまま使うため環境という概念自体がありませんが、Managed Agentsではサンドボックスがどこでどう動くかをenvironments.createで明示的に定義します。クラウド版ならconfig: {"type": "cloud", "networking": {"type": "unrestricted"}}のように、ネットワークアクセスの範囲まで環境側で決めます。

SDKが自動でやっていた機能はクライアント側の責務になる

Anthropicがループを実行してくれる代わりに、SDKが自動でこなしていたいくつかの機能はクライアントが引き受けることになります。これは移行時に見落としやすい落とし穴です。

SDKの機能Managed Agentsでの対応
Plan modeManaged Agentsでの対応計画専用のセッションを先に走らせ、続けて実行用のセッションを走らせる
Output styles・スラッシュコマンドManaged Agentsでの対応user.messageを送る前、またはagent.messageを受け取った後にクライアント側で適用する
PreToolUse / PostToolUseフックManaged Agentsでの対応クライアントはすでに全てのagent.custom_tool_useイベントを応答前に見ている。ロジックはそこに置く。ビルトインツールにはpermission_policy: always_askを使う
max_turnsManaged Agentsでの対応クライアント側でターン数をカウントする

PreToolUseフックに複雑なガード処理を書いていたチームほど、この移行で「フックが無くなった」と誤解しがちですが、実際にはagent.custom_tool_useイベントのハンドラーに同じロジックを移すだけで等価な制御が可能です。

バージョン固定で段階的に切り替える

system_promptCLAUDE.md階層をそのつどプロセス起動時に読み込んでいたAgent SDKと違い、Managed Agentsではエージェントを更新するたびにサーバー側で新しいバージョンが作られます。セッションは特定のバージョンに固定して作成できるため、システムプロンプトを変更したら即座に全セッションへ反映されるわけではありません。新バージョンを一部のセッションだけに向けて動作を確認し、問題なければ残りのセッションも新バージョンへ切り替える、という段階的なロールアウトが可能です。手書きループでシステムプロンプトの変更を反映するにはデプロイが必要でしたが、Managed Agentsではセッション作成時に渡すversionの値を変えるだけで済みます。ロールバックも同様に、直前のバージョン番号を指定してセッションを作り直すだけです。

移行チェックリスト

  1. 必要なネットワークとランタイムを持つ環境をenvironments.createで作成する。クラウド版ならconfig.typecloudにしてネットワーク範囲を指定し、ローカルの実行環境をそのまま使いたい場合はlocalを選ぶ
  2. システムプロンプトとツール選定をエージェント定義(agents.create)へ移植する。ここで登録したnamesystemtoolsはサーバー側でバージョン管理される
  3. 自前のループをsessions.createsessions.events.streamに置き換え、user.messageの送信とsession.status_idleの受信を軸に組み直す
  4. エージェントが読むローカルファイルはFiles APIでアップロードし、resourcesとしてマウントする
  5. カスタムツールのハンドラーを、agent.custom_tool_useイベントへの応答としてイベントループの中に移し、custom_tool_use_id付きで結果を返す
  6. 本番トラフィックを新しいフローへ切り替える前に、テストセッションで一通りの挙動を検証する

移行時によくあるつまずき

  • agents.updateversionを渡し忘れる: モデルやツール構成を更新するAPIは、更新対象の現在のversionを必須で要求します。省略すると更新自体が拒否され、「フィールドを変えたのに反映されない」という手戻りにつながります。取得済みのagent.versionを毎回渡す習慣にしておきます。
  • max_turnsのカウントをそのまま残してしまう: Agent SDKではmax_turnsを渡せばSDK側がターン数を数えて止めてくれましたが、Managed Agentsにはこの引数がありません。無限ループを避けたいなら、クライアント側でagent.messageagent.custom_tool_useイベントの受信回数を自分で数える処理を追加し忘れないようにします。
  • 環境を作らずにセッション作成を試みる: Agent SDKのcwdはローカルパスを直接指すため、環境という概念そのものがありません。Managed Agentsではenvironment_idが必須パラメーターなので、移行チェックリストの1番目を後回しにするとセッション作成そのものでエラーになります。
  • prefillの削除を「壊れた」と誤解する: 新しいモデルでアシスタントメッセージのprefillが廃止されても、イベントベースのセッションモデルはそもそもprefillという概念を持たないため、実装上は何も変更が要りません。Messages APIのモデル移行ガイドを読んで「対応が必要な変更点」として扱ってしまうと、存在しない作業を探すことになります。

モデルバージョンの移行はエージェント定義の1フィールド変更で済む

新しいClaudeモデルがリリースされたとき、Managed Agentsの移行は多くの場合エージェント定義のmodelフィールドを書き換えるだけです。次に作成するセッションから新しいモデルが使われます。

client.beta.agents.update(
    agent.id,
    version=agent.version,
    model="claude-opus-5",
)

Messages APIのモデル移行ガイドに書かれている挙動変更のうち、大半はクライアント側の対応が不要です。max_tokensのデフォルト値やthinking設定はManaged Agentsのランタイムが処理し、エージェント定義には露出しません。ツール引数のJSONエスケープもランタイム側でパース済みの構造化データとして渡ってくるため、生の文字列を自分でパースする必要はありません。「モデルが何を変えたか」という挙動の説明はそのまま参考になりますが、「リクエストコードをどう変えるか」という手順の部分はManaged Agentsには当てはまらないので読み飛ばせます。

まとめ

Messages APIの手書きループからの移行は、会話履歴管理・ツール実行ループ・サンドボックス管理を手放せる代わりに、agent.custom_tool_useイベントへの応答という新しい責務が生まれます。Agent SDKからの移行は設定オブジェクトのマッピング作業が中心で、Plan mode・Output styles・PreToolUseフックのようにSDKが暗黙にやっていた機能はクライアント側で明示的に組み直す必要があります。MCPサーバーを使っている場合はManaged AgentsのMCP接続、webhookで状態変化を監視したい場合はManaged Agentsのwebhookも合わせて確認すると、移行後の運用設計まで一通りカバーできます。Managed Agents全体の設計思想はManaged Agentsの設計思想にまとめています。

この記事を共有:XはてブLinkedIn