エージェントの実行環境を移すとき、次の処理を決めるロジックまで書き換える必要はない。
2026年9月3日のAWS公式記事は、既存のLangGraphをAgentCoreへ移す段階と、Strandsでモデル中心のループへ組み替える段階を分けている。[1]
公開コードを追うと、その違いは「どこで動くか」だけでなく、分岐、会話の保存先、ツールの接続、そして検証できる範囲に現れる。
この記事では、カスタマーサポート用の公開サンプルを使い、グラフを維持する移行と、計画をモデルへ任せる再設計を比較する。クラウド移行の作業量と、応答品質を変える作業を切り分けたい開発者向けの解説である。
2026年9月10日時点で、サンプルのコミット 5c70a427d7764da7fda3613e0169ed1037c5b36e を確認した。ソース解析、付属の変更行数計測、依存パッケージを読み込まない関数の限定実行を行った。AWSへのデプロイ、実モデルの推論、サンプルの依存パッケージを使う全テストは実施していない。以下の結果はその範囲で読む必要がある。
実行基盤と、次の一手を決める仕組み
まず役割を分ける。
実行基盤は、プロセスを起動し、セッションを扱い、外部ツールへ接続し、状態を保存する。次の一手を決める仕組みは、問い合わせを分類し、必要なツールを選び、追加調査や終了を判断する。
AWSのサンプルは、同じ出発点から次の構成を示す。Stage 0〜2という呼び方も公開リポジトリに合わせた。[2]
| 比較点 | Stage 0:自己管理のLangGraph | Stage 1:グラフをAgentCoreへ移す | Stage 2:Strandsへ組み替える |
|---|---|---|---|
| 外側の制御 | 明示したノードと分岐 | Stage 0のグラフを再利用 | モデルのツール要求を中心に反復 |
| 問い合わせ分類 | LLMがラベルを返す | 同じ分類処理 | 独立した分類ノードがなくなる |
| 会話状態 | プロセス内のチェックポインター | AgentCore Memoryを使うチェックポインター | AgentCore Memoryを使うセッションマネージャー |
| ツール接続 | ローカル定義からHTTPを呼ぶ | 一部をGateway経由へ交換 | 実演コードでGatewayのツールを注入 |
| 主な確認対象 | 元の業務フロー | 状態・ツール・入出力の接続 | 判断方針と業務上の振る舞い |
ここでのStage 2は、リポジトリの実演経路を指す。後述するように、単独のRuntimeエントリーポイントとGateway付きの実演コードは同じ接続状態ではない。
LangGraphも、すでにモデルを使って判断している
Stage 0のグラフには4つのノードと2つの条件付き分岐がある。ノードは classify_intent、escalate、assist、tools だ。[2]
この構造を「決定論的なLangGraphと、非決定論的なStrands」とだけ説明すると、実装を取り違える。
classify_intent はモデルを呼び出し、その返答を正規化している。Pythonの分岐が決定論的なのは、入力されるラベルが既に決まっている場合だ。さらに assist の内側では、モデルがツールを使うかどうかを選ぶ。
制御構造を数式で表すなら、入力を $x$、正規化後のラベルを $L$、ルーターを $g$ として、
\begin{aligned}
&P(\mathrm{route}=\mathrm{escalate}\mid x)\\
&\quad=P(g(L)=\mathrm{escalate}\mid x)\\
&\quad=P(L=\mathrm{escalate}\mid x).
\end{aligned}
ルーターが確定的でも、上流の分類誤りは消えない。グラフが与えるのは、どのラベルからどの処理へ進むかをコードで限定できる性質である。
実際のサンプルには、もう一つ具体的な判断がある。モデルの返答を前後の空白除去と小文字化で正規化し、許容する2語に一致しなければ assist にする。ソースから該当関数を抽出し、固定の返答を返す代役モデルで確かめた結果は次のとおりだった。
| 代役モデルの返答 | 保存されるラベル | 分岐先 |
|---|---|---|
escalate |
escalate |
escalate |
ESCALATE |
escalate |
escalate |
assist |
assist |
assist |
escalate because the customer is angry |
assist |
assist |
| 空文字列 | assist |
assist |
これはモデル精度の測定ではない。モデルがどの返答を出した場合に、コードが何をするかの確認である。
不正なラベルを再分類するか、人に回すか、通常応答に進めるかは業務要件で決める必要がある。構造化出力でラベルの形式を制限しても、問い合わせ自体を正しく分類できるかは別に評価する。
なお、サンプルの escalate は定型メッセージと escalated=True を返す。担当者のキューへチケットを作る外部API呼び出しは、このノードにはない。「担当者へ引き継ぐ」と表示することと、実際の引き継ぎ完了は区別して読む必要がある。[2]
Stage 1で維持するもの、交換するもの
Stage 1の agent_runtime.py は、Stage 0から build_graph をインポートする。グラフ定義を複製して修正する構造ではなく、同じ関数へ異なる部品を渡している。[2]
その関係を抜粋すると、次の形になる。初期化や環境変数の読み取りを省いた説明用コードである。
from examples.stage0_langgraph.agent import build_graph
graph = build_graph(
llm=llm,
tools=gateway_tools(),
checkpointer=AgentCoreMemorySaver(memory_id, region_name=region),
)
移行の単位は、実行エントリーポイント、ツール接続、状態の保存先である。route_intent やグラフの辺を触らずに、この3か所を交換できる。
ただし、同じグラフをインポートした事実だけで、回答が完全に一致するとは言えない。ツール名、エラーの表現、返却データ、応答時間が変われば、同じモデルが選ぶ次の行動も変わり得る。
ツールは3個のうち2個を移す
このサンプルでGatewayへ移すのは lookup_order と process_return で、search_faq はローカル定義を残す。
Gatewayから取得したツールには supportTools___lookup_order のような名前が付く。アダプターは、その入力スキーマをLangChainの StructuredTool へ渡し、末尾のツール名が一致するローカル定義を置き換える。Gatewayにないツールは保持する。[2]
ここには二つの接続契約がある。一つは、モデルへ公開する名前と入力スキーマ。もう一つは、モデルが要求した呼び出しを、実際のMCPクライアントへ渡す経路だ。
サンプルのアダプターは戻り値のテキストブロックを連結し、非テキストのブロックは取り込まない。したがって画像や構造化された結果まで扱う用途なら、そのままの変換で十分かを確かめる必要がある。また、ツールが後で呼ばれる時点までMCPクライアントを生存させる構成になっている。
同じツール名が並ぶことだけで移行完了とせず、引数が保たれるか、エラーが読めるか、必要な結果の型が落ちていないかを確かめる。この確認は、計画をモデルに任せる構成でも必要になる。
「記憶がある」を、識別子と保存対象へ分解する
今回扱う保存は、会話や途中状態をプロセスの寿命から切り離すためのものだ。サンプルは長期知識を抽出する戦略を設定せず、strategies=[] で生のイベントを利用する。新規作成時の保持期間には30日を明示している。既存のMemoryリソースを再利用する場合は、その設定を維持して読み戻す。[2]
Stage 1の呼び出し設定では、Runtimeの context.session_id をLangGraphの thread_id に渡し、actor_id も指定する。この対応が変わると、保存してあっても元の会話を探せない。
| 識別子・設定 | サンプルでの役割 | 移行時に決めること |
|---|---|---|
memory_id |
保存先のMemoryリソース | どの環境・データ領域を使うか |
actor_id |
イベントの帰属を区別 | 認証済みの主体とどう対応させるか |
thread_id / session_id
|
続ける会話を特定 | どの呼び出しを同じ会話へ戻すか |
| 保持期間 | 古いイベントが残る期間 | 何日後まで再開を期待するか |
AWSの公式文書は、Runtimeがセッションとユーザーの対応を強制するものではなく、クライアント側のバックエンドが管理する、と明記している。microVMの隔離と、利用者に正しいセッションを割り当てることは別の責任である。[3]
サンプルのStage 1では、actor_id は環境変数、なければ固定文字列から取る。これは各リクエストの認証済みユーザーを自動で識別している実装ではない。複数利用者へ広げるなら、保存先のキーと認可を一緒に設計する必要がある。
また、同じMemoryリソースを使うことと、既存会話を別フレームワークへ移行できることも異なる。今回のウォークスルーはStrands側に別のセッションIDを使うため、LangGraphのチェックポイントをStrandsへ変換して引き継いだ実証ではない。[2]
復旧地点の保存と、外部操作の完了
LangGraphのチェックポイントは、グラフの状態を保存し、会話の継続や中断からの再開に使う。過去のチェックポイントを指定してリプレイした場合、その後のノードは再実行され、LLMやAPIの呼び出しも再び起きる。[4]
したがって、チェックポイントの永続化だけで、外部操作が一度だけ実行されるとは言えない。例えば返品登録が外部で完了した直後、結果を保存する前にプロセスが落ちれば、「外部では成功、手元では未確認」という状態が残る。
この場合の復旧は、会話履歴を読み込むだけでは終わらない。外部APIが持つ操作IDや冪等性キーで結果を照合できるか、再実行してよい処理かを確認する。LangGraphの公式文書も、再開時に繰り返され得る副作用について冪等性を求めている。[5]
この点はランタイムの評価項目であり、モデルの賢さで代替するものではない。
Stage 2では、業務上の振る舞いを改めて確かめる
Strandsの基本ループは、モデルを呼び、ツール要求があれば実行し、結果を会話へ戻して再びモデルを呼ぶ構造である。最終応答などの停止理由によってループが終了する。[6]
調べるべき情報やツールの順序が問い合わせごとに大きく変わる場合、この形は柔軟に扱いやすい。一方、必ず通すべき業務処理がある場合は、モデルへ渡す指示だけでなく、実行前後のチェックや明示した制御として実装する選択がある。
ここでも、LangGraphは固定手順しか書けず、Strandsは制御できない、という製品の二分法にはしない。比較しているのは、今回のサンプルが選んだ構成だ。
コードから消える制約を数える
Stage 0のプロンプトには、注文の状態を推測せずツールを呼ぶ指示があり、人への対応を求める問い合わせを分類するための専用プロンプトもある。
確認したStage 2の SYSTEM_PROMPT は一般的なサポート役を指定する1文で、Stage 0の分類ノード、ルーター、エスカレーション用ノードは使わない。モデルIDが同じでも、渡す指示と制御が変わっている。[2]
従って、この差分は、同じ業務動作が自動的に引き継がれたという証拠にはならない。移行前の要件を次のような観測可能な条件に直し、再設計後のトレースで確認するとよい。
- 注文番号が分からない時点で、架空の注文状態を回答していないか。
- 利用者が人による対応を求めたとき、実際の引き継ぎ処理へ到達するか。
- ツールが失敗したとき、成功したと説明していないか。
- 途中で再起動したとき、同じ会話へ戻り、未確認の操作を照合できるか。
これは本稿が提案する評価条件であり、AWSサンプルがこれらすべてを満たすと測定したものではない。
実演コードと、デプロイ用入口も区別する
リポジトリの run_walkthrough.py は、Stage 2で build_agent(extra_tools=...) にGatewayから取得したツールを渡し、ローカルのプロセスからエージェントを呼ぶ。
一方、Stage 2の単独Runtimeエントリーポイントが通る support_agent() は、確認したコミットでは extra_tools を渡していない。この経路ではローカル定義のツールが選ばれる。Stage 1のRuntimeをデプロイするコードがあることから、Stage 2のGateway接続まで同じように完成しているとは判断できない。[2]
Gateway付きのStage 2を本番へ持ち込むなら、入口からツール注入までの接続、クライアントの寿命、実際に呼ばれたツール名を確かめる工程が要る。READMEの構成図に加え、実際の呼び出し経路を追う理由がここにある。
さらに、発見できたGatewayツールだけをローカル実装と置き換える方式では、発見できなかったツールがローカルに残る。権限制御でツール一覧が絞られる場合に、禁止したかった操作が別経路で残らないかも確認が必要だ。この注意点は、リポジトリ内の verify_policy_visibility.py にも記されている。本稿では、そのファイルに記録されたクラウド上の測定を再実行していない。[2]
Stage 1では共有するグラフへ呼び出しごとに会話IDを渡す。Stage 2のセッションマネージャーは構築時にIDへ結び付くため、サンプルはセッションIDごとにAgentをキャッシュする。複数主体を同じプロセスで扱う構成へ変更するなら、キャッシュのキーにもその帰属を反映する必要がある。
変更行数から分かること、分からないこと
公開サンプルには、移行の差分を数える verify_diff_claim.py がある。確認したコミットで次を実行した。
python3 -m examples.validation.verify_diff_claim
結果は、エージェントの入口で削除6行・追加39行の計45行、追加のツールアダプターが22行、再利用するグラフ・プロンプト・ツール本体が85行だった。
ただし、この計測はコメント、docstring、空行、サンプルのコンソール表示などを除外する。テストを含まず、デプロイ用コードの422行も別枠で表示する。元のエージェントがモデル・ツール・保存先を引数で差し替えられるようになっている点も、移行を小さくする前提だ。[2]
この数字は、特定のサンプルにおける責任の分割を示す。「実システムも45行で移行できる」「運用工数がその比率で減る」という測定ではない。
本番向けの見積もりでは、変更行数に加えて、認証済みユーザーと会話の対応、ツールの結果形式、状態の保持期間、復旧試験、デプロイ手順、応答品質の再評価を確認する。行数が小さくても、境界で変える契約が多ければ検証には時間がかかる。
どちらの構成を選ぶか
既存の分岐が業務要件をよく表しており、主な問題がプロセスの管理や会話状態の保存なら、まずグラフを保って実行環境を移す構成が合う。変える箇所を限定でき、接続の不具合と判断方針の変化を区別しやすい。
問い合わせごとに調査手順が大きく変わり、固定した分岐の追加が負担になっているなら、モデル中心のループを検討する。その際は、消える分岐が担っていた要件を洗い出し、指示・ツールの実装・明示した制御・評価のどこへ移すかを決める。
両者を組み合わせることもできる。例えば外側に受付・確認・引き継ぎのグラフを置き、調査ノードの内側ではモデルがツールを選ぶ。今回のStage 0にも、既にその構造がある。
AgentCoreへの移行で先に決めたいのは、何を保存し、どこから再開し、どの判断をコードで固定するかである。その境界を保ったまま実行基盤を移せるなら、計画方法の変更は、次の独立した設計判断として扱える。
参考リンク
[1]AWS — Migrate agentic workloads to Amazon Bedrock AgentCore(2026年9月3日)
[2]AWS Samples — sample-migrate-agents-to-amazon-bedrock-agentcore(MIT-0、確認したコミットに固定)
[3]Amazon Bedrock AgentCore — Use isolated sessions for agents
[4]LangGraph — Persistence
[5]LangGraph — Interrupts:Side effects called before interrupt must be idempotent
[6]Strands Agents — Agent Loop