この記事はバージョン Spring ’26 において執筆しています。
現在の動作と異なる場合がありますので、ご認識おきください。
Agent Scriptでエージェントを構築する際、「ユーザーの要望をもとにケースを作成し、その直後に人間(オペレーター)にエスカレーションする」といった連続したフローの実装課題に直面する開発者は少なくありません。
この記事では、Agent Scriptの学習初期に陥りやすい「冗長なサブエージェント遷移」のアンチパターンを紐解き、Agentforceのアーキテクチャに最も適した「状態管理(ステートマシン)による順次処理」を用いて、1つのサブエージェント内で確実かつ安全に統合する実践的な手法を解説します。
アンチパターン:処理ごとにサブエージェントを分けてしまう冗長な設計
Agent Scriptの導入初期は、処理の役割ごとにサブエージェントを細かく分割する設計が採用されがちです。
- Subagent A (ケース作成用): ユーザーから情報を聞き出し、ケースを作成する。
- Subagent B (エスカレーション用): ケース作成完了後、オペレーターへ引き継ぐ。
この設計は動作可能ですが、実運用においては明確な課題が生じます。サブエージェント間を遷移するたびにLLMの推論ターンが余分に発生し、わずかですがユーザーの待ち時間(レイテンシ)が増加してしまうのです。
課題:@utils.escalate の仕様推定
筆者の検証と各種公式情報によると、と推定されます。@utils.escalate は推論エンジン専用のツールである
- 公式サンプルにおける定義場所が「reasoning.actions」であること
公式ドキュメントの『Utils.escalate: Escalate to Human Rep』のサンプルコードにおいて、@utils.escalate は外部のFlow等を定義するsubagent.actionsではなく、reasoningブロック配下のactions:内に定義されています。Agent Scriptのアーキテクチャ上、このreasoning.actionsは「LLMが推論の過程で自律的なツールとして使おうと判断した際の、バインディングルールを定義するための場所」であり、LLMが利用できる「武器(ツール)のカタログ」として機能します。 - 決定論的な
runではなく、自然言語のプロンプト(|)で実行を促す設計であること
システム側で推論を挟まずに強制的・決定論的にアクションを実行させる場合、Agent Scriptではrunコマンドを使用します。しかし、公式のサンプルコードでは@utils.escalateをrunで呼び出すのではなく、instructions:ブロックの中で| call the action...といった自然言語のプロンプトを用いて、LLMに対してツールの実行を指示(ガイド)する構文が採用されています。この公式の記法自体が、システムによる直接的な強制実行ではなく、LLMの推論を介するツールであることを示しています。 - 「available when」によるLLMへの露出制御が前提となっていること
根拠となるコードは上記と同じです。@utils.escalateなどの組み込みユーティリティ機能は、available whenパラメータと組み合わせて使用されることが一般的です。このavailable whenは「推論エンジン(LLM)に対して、いつそのツールを公開(利用可能に)するかを条件付きで制御する」ための専用パラメータです。もしこれがシステム側で強制発火させるコマンドであれば、「LLMへのツールの露出を制限する」というアプローチ自体がアーキテクチャとして矛盾することになります。
▼公式のサンプルコード
subagent my_topic_name:
reasoning:
instructions:
| call the action {!@actions.escalate_to_human} if the user wants to speak with a human rep
actions:
escalate_to_human: @utils.escalate
description: "Call this when you need to escalate to a human rep"
available when @variables.in_business_hours
これら「定義される階層」「呼び出しの構文」「アクセス制御の方法」というAgent Scriptの仕様の組み合わせより、@utils.escalate を決定論的なシステムコマンドではなく「LLMに利用を委ねるツール」として利用するものと推定しました。
最適解:変数(状態)による条件分岐とツールの露出制御
この仕様の壁を突破する最適解が、変数の状態(ケースIDの有無など)をフラグとして活用し、if 文によって実行フェーズを厳格に管理するアプローチです。
比較:冗長な設計 vs 状態管理による統合
| 比較項目 | Before: 2段階サブエージェント(冗長) | After: 状態管理と条件分岐(最適解) |
|---|---|---|
| 構造 | アクションと遷移でファイルを分割 | 1つのファイル内で変数の状態に応じてフェーズを移行 |
| エスカレーション | LLMの自然言語解釈による遷移 | ケース作成が成功した状態でのみツールとして許可 |
| パフォーマンス | 遷移ごとに推論ターンが発生し遅い | 1つのコンテキスト内で処理されるため効率的 |
実装コード:ステートマシンとしてのAgent Script
以下のコードは、ケースが作成されたかどうか(@variables.caseRecordId)を変数で状態管理し、「ケース作成」から「エスカレーションのプロンプト指示」へと安全に処理をフェーズ移行させる実装例です。
...
variables:
...
caseRecordId: mutable string = ""
description: "The Case Record ID that generated by Flow"
userIntent: mutable string = ""
description: "User intent"
isCreateCaseProcessed: mutable boolean = False
description: "True if the case creation process has been completed"
...
subagent Custom_Escalation:
description: "顧客の問い合わせを処理し、必要に応じてエスカレーションします。"
reasoning:
actions:
escalate_to_human: @utils.escalate
description: "Escalate the conversation to a live human agent."
available when @variables.caseRecordId != ""
instructions: ->
# 前提: 前段の処理等で @variables.userIntent に "escalation" がセットされているものとする
# エスカレーション対象の会話でない場合は要件をヒアリング
if @variables.userIntent != "escalation":
| どのような要件か聞き出してください。
# エスカレーション対象の会話である場合はケース作成
if @variables.userIntent == "escalation":
# 決定論的ロジック:ケースの強制作成
run @actions.custom_create_case
with caseSubject = "【自動作成】オペレーターエスカレーション"
with caseDescription = "ユーザーよりオペレーターへの引き継ぎ要望がありました。"
with verifiedCustomerID = @variables.ContactId
with messagingSessionID = @variables.RoutableId
# アクションの出力結果を変数にセット
set @variables.caseRecordId = @outputs.caseRecord.id
set @variables.isCreateCaseProcessed = True
# ケース作成が成功(IDが存在)する場合にのみ即座にエスカレーションへ遷移
if @variables.userIntent == "escalation" and @variables.caseRecordId != "" and @variables.isCreateCaseProcessed == True:
| 即座に {!@actions.escalate_to_human} を実行し、これまでのやり取りを引き継いで担当者に繋ぎます。
# ケース作成が失敗している場合は転送処理の不具合を知らせる
if @variables.userIntent == "escalation" and @variables.caseRecordId == "" and @variables.isCreateCaseProcessed == True:
| 転送処理に不具合があったことと時間をおいて再度試すようにお客様に伝えてください。
actions:
...
設計のポイント
この実装は、Agent Script内で「システムによる決定論的処理」と「LLMによる自律的アクション」を安全に同居させるためのベストプラクティスが詰まっています。ポイントは以下の3点です。
- 処理完了フラグ(
isCreateCaseProcessed)を用いた厳格なフェーズ管理
ケースID(caseRecordId)が空("")である状態には、「まだケース作成処理を行っていない状態」と「処理を行ったがエラー等でIDが取得できなかった状態」の2つの意味が含まれます。ここでisCreateCaseProcessedという可変なBoolean変数を導入し、run実行直後にTrueへ更新(set)することで、後続の成否判定(フェーズ3および4)が「確実にアクション処理後であること」を保証しています。予期せぬタイミングでのエラー発火を防ぐ、堅牢な設計です。 available whenとインライン呼び出し({! })による確実なツール実行@utils.escalateのようなLLM駆動のツールは、自律的に発火してしまうリスク(ハルシネーション)があります。そのため、available when @variables.caseRecordId != ""を指定し、「ケース作成が成功した状態」になるまでツールをLLMから完全に隠蔽しています。その上で、条件を満たしたフェーズ3のプロンプト内で{!@actions.escalate_to_human}とインライン指定することで、LLMに対して「今すぐ確実にこのツールを使用せよ」という強い実行命令(ガイドライン)を与えています。- 1つのコンテキストで完結する順次制御(Action Chainingの昇華)
処理ごとにサブエージェントを分割するのではなく、トップレベルで定義した変数をsetコマンドで更新しながらif文を上から下へ流していくことで、1つのブロック内で「ヒアリング → 強制処理(ケース作成) → 成功・失敗の判定 → エスカレーション遷移」という一連のステートマシン(状態遷移)を実現しています。これにより、LLMの無駄な推論ターンを削減し、パフォーマンスと安全性を両立させています。
まとめ
Agent Scriptでは、@utils.escalate のようなLLM駆動のツールと、システム駆動の決定論的ロジック(run)を混同せず、それぞれの仕様を正しく理解することが重要です。
変数を活用した状態管理(ステートマシン)のパラダイムを取り入れることで、冗長なサブエージェント分割を避けつつ、AIの柔軟性とシステムの確実性を両立した高度なエージェントを構築することができます。



読者の声