はじめに
AgentCore Harnessを利用してマルチエージェントは作成、利用できるのでしょうか?
今回は気になったので調べつつ検証しました。決してAgentCore Harnessに対するネガキャン記事ではないことを御留意ください。
いきなり結論
使ったことがある方はお気づきかと思いますが、AgentCore Harnessは複数のエージェントを利用する構成よりも一つのエージェントに対してその周りの動作環境(ツールやskills、モデルなど)を簡単に整備できるようになっています。これはハーネスの作成画面でも設定項目が多岐に渡ることからわかると思います。
では、よくあるマルチエージェントの構成はできるのか..?という点なのですが、難しそうです。
ちょうど同じような疑問が以下のブログに書かれています。
Can I use the harness for multi-agent orchestration?
The harness manages one agent. For multi-agent workflows, you can chain invocations through Step Functions integration. True multi-agent coordination (delegation, negotiation, shared state) requires custom orchestration on top.
ハーネスを使ってマルチエージェントのオーケストレーションを行えますか?
ハーネスは1人のエージェントを管理します。マルチエージェントワークフローでは、Step Functions統合を通じて呼び出を連鎖させることができます。真のマルチエージェント協調(委任、交渉、共有状態)には、その上にカスタムオーケストレーションが必要です。
つまり、AgentCore Harness単体では私たちが今までイメージしていたマルチエージェントのオーケストレーションは構築することができず、StepFunctionsのようなサービスと組み合わせる必要があるのです。
StepFunctionsと組み合わせてそれっぽいのは作れる
StepFunctionsを利用した場合、以下のようなワークフローによってマルチエージェントっぽいものは動作させることができます。
この方法はノードの分だけ独立したエージェントを作成してワークフロー組む手法です。厳密には他にも構築方法はありそうですが、エージェント同士が協調して動作するマルチエージェントの構成ではなく、あくまで独立したエージェントが結びついているだけと考えるべきです。
構築例
作成したエージェントは全部で2つ(5つ)です。
- 調査役
- まとめ役(レビュワー1~3、レポート作成役)
まとめ役に関しては、エージェントごとの人格(システムプロンプト)を変更して、動作させています。
そのため、コンソールから確認できるエージェントは2つのみとなります。
まず、以下のように大元となる2このハーネスエージェントを作成しています。
// 一体目:調査エージェント
const researchHarness = new bedrockagentcore.CfnHarness(this, 'ResearchHarness', {
harnessName: researchHarnessName,
executionRoleArn: researchRole.roleArn,
model: {
bedrockModelConfig: {
modelId: MODEL_ID,
temperature: 0.3,
},
},
systemPrompt: [
{
text: 'あなたはプロのリサーチャーです。ブラウザツールでWeb上の信頼できる情報を調査し、要点・数値・出典を整理して簡潔に報告してください。',
},
],
tools: [
{
type: 'agentcore_browser',
name: 'browser',
config: { agentCoreBrowser: {} },
},
],
});
//レビュー、まとめ役:4体分になる
const textReasoningHarness = new bedrockagentcore.CfnHarness(this, 'TextReasoningHarness', {
harnessName: textReasoningHarnessName,
executionRoleArn: textReasoningRole.roleArn,
model: {
bedrockModelConfig: {
modelId: MODEL_ID,
temperature: 0.3,
},
},
// デフォルトのシステムプロンプト。実際の人格はStep Functionsの各Taskで上書きする。
systemPrompt: [{ text: 'あなたは有能なビジネスアシスタントです。' }],
memory: { disabled: {} },
maxIterations: 15,
timeoutSeconds: 180,
});
AgentCore HarnessはL1コンストラクトのCDKにて作成可能です。ほぼCfnなので、まだコンソールから作成した方がいいか...
次に、システムプロンプトによってレビュー、まとめ役のエージェントに4人分の振る舞いをさせます。
const reviewPersonas: { id: string; suffix: string; label: string; prompt: string }[] = [
{
id: 'TechnicalReview',
suffix: 'review-tech',
label: '技術',
prompt:
'あなたは技術レビュアーです。実現可能性・技術的リスク・必要な技術スタックの観点から、与えられたリサーチ結果を評価してください。',
},
{
id: 'BusinessReview',
suffix: 'review-biz',
label: 'ビジネス',
prompt:
'あなたはビジネスレビュアーです。market fit・コスト対効果・競合優位性の観点から、与えられたリサーチ結果を評価してください。',
},
{
id: 'RiskReview',
suffix: 'review-risk',
label: 'リスク',
prompt:
'あなたはリスクレビュアーです。法規制・セキュリティ・運用上のリスクの観点から、与えられたリサーチ結果を評価してください。',
},
];
const writerTask = invokeHarnessTask(this, 'Writer', {
harnessArn: textReasoningHarness.attrArn,
systemPrompt:
'あなたはプロのライターです。調査結果と複数の専門家レビューを統合し、要点が明確で読みやすい最終レポートを日本語で執筆してください。',
messageTextJsonPath: writerMessageText,
sessionSuffix: 'writer',
resultPath: '$.writing',
maxIterations: 15,
timeoutSeconds: 180,
catchNext: 'WorkflowFailed',
});
なぜシステムプロンプトによる振る舞いの切り替えをしているのか?必要な分だけハーネスエージェントを作ればいいじゃん。と思うかもしれません。これは正直、トレードオフな考えで、私の構築例はあくまで一例です。役割ごとにハーネスエージェントを作成するのもありだと思います。
使い回し設計のトレードオフ:Observabilityで見分けがつかなくなる問題
まず、なぜこんなことができるのかという話ですが、InvokeHarnessというAPI自体が「呼び出し時に渡したパラメータは、そのハーネスに保存されているデフォルト値を1回だけ上書きする」という仕様になっています。実際、SDKのリファレンスには各パラメータの説明にこう書かれています。
Model: "The model configuration to use for this invocation. If specified, overrides the harness default."
AllowedTools: "...If specified, overrides the harness default."
つまり systemPrompt も model も tools も、ハーネス作成時(CreateHarness)に設定した値はあくまで「デフォルト」であって、呼び出し時(InvokeHarness)に指定すればその回だけ差し替えられる、という正式な仕様です。ハーネスのリソース自体が書き換わるわけではないので、「1つのハーネスを人格違いで使い回す」というのは別に裏技でもなんでもなく、公式にサポートされた使い方だったりします。
というわけで、CDKコード・IAMロールの数を最小限に抑えられるので採用したのですが、実際に動かしてみると代償がありました。AgentCore ObservabilityのCloudWatchダッシュボードを見ても、4つの呼び出し全部が同じハーネス名(text_reasoning_harness)で表示されてしまい、どれが技術レビューでどれがライターだったのか、パッと見て区別がつかないのです。
実際にCloudWatch Logsを覗くと、こんな感じで4つの独立したログストリームが記録されていました。
見た目上は全部「同じエージェント」の記録なので、中身(システムプロンプトやSessionId)を確認しないと正体がわかりません。今回はRuntimeSessionIdに-review-techのようなサフィックスを仕込んでおいたので、それを手がかりに識別できましたが、正直これは運用でカバーしている状態です。
改めて比較すると、こんなトレードオフになります。
| 1つのハーネスを使い回す | 役割ごとに別ハーネスを作る | |
|---|---|---|
| ハーネス・IAMロールの数 | 少ない | 役割の数だけ増える |
| CDKコードの見通し | シンプル | ほぼ同じ定義の繰り返しになりがち |
| Observability上の見分けやすさ | ❌ 全部同じ名前で紛らわしい | ✅ ハーネス名自体で一目瞭然 |
| 役割ごとにモデル・ツールを変えたい時 | 呼び出し側のオーバーライドで頑張る | ハーネス自体を直接編集できて素直 |
「リソース数を減らすシンプルさ」と「運用時に何が起きたか一目でわかるシンプルさ」はトレードオフの関係にあって、今回は前者を優先しましたが、本番運用やチームで見る前提なら後者を取って役割ごとにハーネスを分けた方が親切かもしれません。このあたりは要件次第で選べばいいと思います。
Lambda durable functionsじゃダメのか
StepFunctionsとよく引き合いに出されるのがLambda durable functionsですが、Lambda durable functionsはどうなのでしょうか。
Lambda durable functionsは、チェックポイント&リプレイの仕組みで長時間実行の処理を実現する新しいLambdaの機能です。context.step()でラップした処理は自動でチェックポイントされ、失敗しても完了済みのステップはやり直さずに再開できます。wait_for_callback()やwait_for_condition()を使えば、人間の承認待ちや外部システムのポーリングも(待機中は課金なしで)組み込めます。最大1年間実行できるので、StepFunctionsのStandardワークフローとほぼ同じ土俵で戦える機能です。
実際、AWSの公式ブログでもマルチエージェントAIワークフローをLambda durable functionsで組む例が紹介されていて、医療の事前承認ワークフローを題材に、エージェントの呼び出し→医師の承認待ち→外部システムへの提出、という流れを1つのPython関数の中に素直に書き下しています。
@durable_execution
def handler(event, context: DurableContext):
clinical_data = context.step(extract_clinical_agent(event["patient_id"]))
necessity = context.step(medical_necessity_agent(clinical_data))
approval = context.wait_for_callback(...)
decision = context.wait_for_condition(...)
とはいえ、StepFunctions側にも技術的なメリットはありまして...
-
bedrockagentcore:invokeHarnessの最適化統合はLambdaレスで、Step Functionsのタスクから直接AgentCoreを呼べる。Durable Functionsだと、結局Lambdaのコード内でboto3等を使ってHarnessを呼ぶことになり、Lambda自体の実行時間・パッケージングを気にする必要がある - コンソールのグラフビューがそのまま可視化ツールになる(この記事で何度も貼ったスクリーンショットの画面)。Durable Functionsの実行状況はCloudWatch/X-Rayのトレースで追う形になり、ワークフロー全体を俯瞰する専用ビューはない
なので結論としては「ダメ」ではなく、向き不向きの問題だと思います。今回のような「順番と並列がほぼ決まっているシンプルなパイプライン」はStepFunctionsの宣言的な統合で十分素直に組めましたが、条件分岐が複雑だったり人間の承認を挟んだりするような、より手続き的なロジックが必要なワークフローならLambda durable functionsの方が書きやすそうです。
Runtimeとの棲み分け
ここまでの内容を踏まえて、HarnessとRuntimeをいつ使い分けるべきかを整理します。
| Harness | Runtime | |
|---|---|---|
| 何を書くか | 設定(モデル・ツール・システムプロンプト) | コード(フレームワークを自分で組み込む) |
| 得意なこと | 単一エージェントを最速で本番投入 | 複雑な制御・マルチエージェント連携 |
| フレームワークの選択 | ❌(Strands固定) | ✅(自由) |
| Graph/Swarmなどの非エージェントループ型パターン | ❌ | ✅ |
| Hooksなどのカスタム制御 | ❌ | ✅ |
| Bidirectional streaming(音声対話など) | ❌ | ✅ |
Harnessは「1体のエージェントを、コードを書かずに本番品質で動かす」ことに極振りしたサービスです。だからこそハーネス作成画面の設定項目が豊富なのだと思います(冒頭のスクショの通り)。逆に言うと、エージェント同士が動的に委任し合ったり、状態を共有しながら協調したりする本当の意味でのマルチエージェントは最初から守備範囲外で、そこを無理に実現しようとするとこの記事でやったような「StepFunctionsでラップする」「1つのHarnessを人格違いで使い回す」といった力技が必要になってきます。
Strands AgentsのAgent-as-Tool・Graph・Swarmのような機構を使って、オーケストレーターと専門家エージェントが同一セッション内で動的にやり取りするような構成を組みたいなら、素直にRuntime上でコードを書いた方がいいでしょう。
まとめ
AgentCore Harness単体では、いわゆる「オーケストレーター+専門家エージェント」のようなマルチエージェント構成は組めません。Harnessが管理するのはあくまで1体のエージェントです。
StepFunctionsと組み合わせれば、複数のHarness呼び出しを繋いで「それっぽい」ワークフローは作れます。ただしこれはエージェント同士が協調しているのではなく、独立したエージェント呼び出しが順番・並列に繋がっているだけ、と捉えるべきです。
動的な委任・交渉・状態共有を伴う本格的なマルチエージェントが必要なら、素直にAgentCore Runtime + Strands Agentsなどでコードを書くことになります。
Harnessのネガキャンをする意図はないのですが、マルチエージェントという文脈で見たときに「できること」と「できないこと」の境界線がどこにあるのかを、時際に確かめることができてよかったと思います。
参考



