AgentKit 2.0 でマルチエージェントシステムを構築すると、単一エージェントでは考えられなかった種類の障害に直面することがあります。あるエージェントが別のエージェントの完了を待って無限に待機し続けたり、並列実行したエージェントが互いに競合してデータを破壊したり、オーケストレーターが部下エージェントの失敗をハンドリングできず全体が止まったりといった状況です。
マルチエージェント協調失敗の7つのパターン
まず障害のパターンを正確に把握することが、迅速な復旧への第一歩です。
パターン1:エージェント間デッドロック
AエージェントがBエージェントの完了を待ち、BエージェントもAエージェントの出力を待っている状態。互いが相手を待ち続けて処理が完全に止まります。
診断
AgentKit 2.0 のアクティビティログで、2つ以上のエージェントが同じタイムスタンプ付近で「Waiting for agent X」状態になっていないか確認します。
パターン2:オーケストレーターの意思決定ループ
Manager エージェント(オーケストレーター)が部下エージェントへの指示を繰り返し生成するが、何も進まないループに入る状態。ログを見ると同じタスク割り当て指示が繰り返されています。
パターン3:部下エージェントのサイレント失敗
部下エージェントがエラーなしに完了したように見えるが、実際には何もしていなかった(または誤った結果を返した)状態。オーケストレーターはその結果を信頼して次のステップに進んでしまいます。
パターン4:コンテキスト汚染
複数のエージェントが共有コンテキスト(共有メモリや共有ファイル)に同時書き込みを行い、データが混乱する状態。エージェントAが書いた情報をエージェントBが上書きするなどして、最終的に不整合なデータが残ります。
パターン5:カスケード障害
1つのエージェントの失敗が、それに依存する他のエージェントを次々と失敗させる連鎖反応。後続エージェントが前のエージェントの出力を前提にしているため、失敗が伝播します。
パターン6:エージェントのリソース競合
複数のエージェントが同じ外部リソース(API、ファイル、データベース)に同時アクセスしてレート制限に引っかかったり、ファイルロックの競合を起こしたりする状態。
パターン7:コンテキスト長超過によるトークン切れ
長時間稼働するマルチエージェントタスクで、エージェントのコンテキストウィンドウが徐々に満杯になり、重要な初期指示が失われる状態。後半になるほどエージェントの動作が劣化します。
診断:どのパターンかを特定する
ログからの診断
AgentKit 2.0 では各エージェントの詳細なアクティビティログを確認できます。以下のログパターンで障害タイプを特定しましょう。
# AgentKit 2.0 でのログ取得(開発環境)
import antigravity_sdk as ag
# デバッグレベルのログを有効化
ag.set_log_level("DEBUG")
# 実行履歴の取得
session = ag.Session.load("YOUR_SESSION_ID")
print(session.get_activity_log(verbose=True))
# 特定エージェントのツール呼び出し履歴
for agent_name, agent in session.agents.items():
print(f"\n=== {agent_name} ===")
for call in agent.tool_calls:
print(f" {call.timestamp}: {call.tool} -> {call.status}")
デッドロックのサイン:複数エージェントが waiting_for_agent 状態で止まっている
ループのサイン:同一のツール呼び出し(特に assign_task)が繰り返されている
サイレント失敗のサイン:エージェントが completed だが出力が空か異常に短い
カスケード失敗のサイン:エラーのタイムスタンプが連鎖的に続いている
タイムアウト状態の確認
# エージェントの現在の状態をチェック
for agent_name, agent in session.agents.items():
state = agent.get_state()
print(f"{agent_name}: {state.status} (経過時間: {state.elapsed_seconds}秒)")
if state.status == "waiting" and state.elapsed_seconds > 300:
print(f" ⚠️ 警告: 5分以上待機中 → デッドロックまたはタイムアウト疑い")
即時復旧:障害パターン別の対処手順
デッドロックの解消
# デッドロック検出と強制解放
def resolve_deadlock(session_id: str, deadlocked_agents: list[str]):
session = ag.Session.load(session_id)
# 1. デッドロックしているエージェントを特定して強制停止
for agent_name in deadlocked_agents:
agent = session.agents[agent_name]
agent.force_stop(reason="deadlock_detected")
print(f"✓ {agent_name} を強制停止しました")
# 2. 共有リソースのロックを解放
session.release_all_locks()
print("✓ 全ロックを解放しました")
# 3. タスクを再スケジュール(先に完了できる方を優先)
# 依存関係グラフを確認して非循環な順序で再実行
task_graph = session.get_task_dependency_graph()
restart_order = task_graph.topological_sort()
for task in restart_order:
if task.name in deadlocked_agents:
print(f"→ {task.name} を再起動します...")
task.restart(clear_waiting_state=True)
オーケストレーターループの解消
Manager エージェントが指示ループに入った場合は、まず現在のコンテキストをダンプして原因を確認します。
def fix_orchestrator_loop(session_id: str, orchestrator_name: str):
session = ag.Session.load(session_id)
orchestrator = session.agents[orchestrator_name]
# ループの原因を診断
recent_actions = orchestrator.get_recent_actions(n=20)
action_types = [a.type for a in recent_actions]
# 同じアクションが繰り返されているか確認
from collections import Counter
action_counts = Counter(action_types)
print("直近20アクションの分布:", action_counts)
# ループ検出: 同じアクションが10回以上
if action_counts.most_common(1)[0][1] >= 10:
print("⚠️ ループを検出しました。コンテキストをクリアして再起動します...")
orchestrator.clear_context(preserve_initial_instructions=True)
orchestrator.restart_with_new_context(
additional_context="前のタスク割り当ては一旦リセットされました。"
"現在完了済みのタスクを確認して、未完了のタスクから再開してください。"
)
コンテキスト汚染の修復
def fix_context_contamination(session_id: str):
session = ag.Session.load(session_id)
# 1. 全エージェントを一時停止
for agent in session.agents.values():
agent.pause()
# 2. 共有コンテキストのスナップショットを取得
snapshots = session.shared_context.get_write_history()
print(f"最後の書き込み履歴 ({len(snapshots)}件):")
for snap in snapshots[-10:]:
print(f" {snap.timestamp} | {snap.agent} → {snap.key}: {str(snap.value)[:50]}")
# 3. 最後に正常だったスナップショットを特定して復元
last_good_snapshot = snapshots[-5] # 5操作前が安全か確認
session.shared_context.restore(last_good_snapshot.id)
print(f"✓ コンテキストを {last_good_snapshot.timestamp} 時点に復元しました")
# 4. 排他制御を追加してエージェントを再起動
session.shared_context.enable_write_locking()
for agent in session.agents.values():
agent.resume()
再発防止:耐障害マルチエージェント設計
障害から復旧したら、同じ問題が起きないように設計を見直しましょう。
デッドロック耐性の設計
# 依存関係の循環を事前に防ぐ設計パターン
class DeadlockSafeOrchestrator:
def __init__(self):
self.task_dependencies = {} # タスク → 依存タスクのマッピング
self.task_timeout = 180 # 秒
def assign_task(self, agent_name: str, task: dict, depends_on: list = None):
"""循環依存チェック付きタスク割り当て"""
if depends_on:
# 循環依存の検出
if self._creates_cycle(task["id"], depends_on):
raise ValueError(
f"循環依存を検出しました: {task['id']} → {depends_on}"
)
self.task_dependencies[task["id"]] = depends_on
# タイムアウト付きでタスクを割り当て
return ag.assign_task(
agent=agent_name,
task=task,
timeout_seconds=self.task_timeout,
on_timeout="fail_gracefully" # タイムアウト時に失敗として扱う
)
def _creates_cycle(self, task_id: str, depends_on: list) -> bool:
"""DFS でサイクル検出"""
visited = set()
def dfs(node):
if node in visited:
return True
visited.add(node)
for dep in self.task_dependencies.get(node, []):
if dfs(dep):
return True
return False
return any(dfs(dep) for dep in depends_on)
タイムアウトとフォールバック戦略
# 全エージェントにタイムアウトとフォールバックを設定する
AGENT_CONFIG = {
"data_collector": {
"timeout_seconds": 120,
"retry_count": 2,
"on_failure": "skip_and_continue", # 失敗しても次のステップへ
"fallback_value": {"status": "skipped", "data": []}
},
"analyzer": {
"timeout_seconds": 300,
"retry_count": 1,
"on_failure": "escalate_to_human", # 人間に通知
"fallback_value": None
},
"reporter": {
"timeout_seconds": 60,
"retry_count": 3,
"on_failure": "use_template", # テンプレート出力を使う
"fallback_value": "分析エラーのため結果を生成できませんでした。"
}
}
コンテキスト長管理
長時間稼働するシステムでは、コンテキストの肥大化を防ぐ定期的な要約が重要です。
class ContextManager:
MAX_CONTEXT_LENGTH = 100_000 # トークン数の目安
def __init__(self, agent):
self.agent = agent
self.message_count = 0
def add_message(self, message: str):
self.agent.context.append(message)
self.message_count += 1
# 一定間隔でコンテキストを圧縮
if self.message_count % 20 == 0:
self._compress_context()
def _compress_context(self):
"""古いメッセージを要約して保持量を削減"""
current_length = self.agent.estimate_context_tokens()
if current_length > self.MAX_CONTEXT_LENGTH * 0.8:
# 古い半分のメッセージを要約
old_messages = self.agent.context[:-10] # 直近10件は保持
summary = self.agent.summarize(old_messages)
self.agent.context = [
f"[以前の作業の要約] {summary}"
] + self.agent.context[-10:]
print(f"コンテキストを圧縮: {len(old_messages)} → 1メッセージ")
本番インシデント対応チェックリスト
マルチエージェントシステムで障害が発生した際の対応手順をまとめます。
検知フェーズ(0〜5分):アラートを確認し、障害の影響範囲と現在進行中のタスクを確認します。ログからどのパターンの障害かを特定します。
封じ込めフェーズ(5〜15分):障害が拡大しないよう、問題のあるエージェントを停止または分離します。共有リソースへの書き込みを一時ロックします。
復旧フェーズ(15〜60分):本記事のパターン別対処コードを参照して修復を実施します。テスト環境で動作確認してから本番に適用します。進捗中だったタスクを安全な地点から再開します。
事後フェーズ:根本原因を分析してコードまたは設定に修正を加えます。同様の障害を検知するアラートを追加します。学びを agents.md または設計ドキュメントに記録します。
個人開発者の視点から(実体験メモ)
ここまでの要点
AgentKit 2.0 のマルチエージェントシステムは強力ですが、エージェント間の協調に失敗するとシングルエージェントよりも複雑な障害が発生します。本記事で解説した7つのパターンと診断方法・復旧コードを手元に置いておくことで、インシデント発生時に落ち着いて対処できるようになります。
そして最も重要なのは、障害が起きた後に設計を改善し続けることです。デッドロック、ループ、コンテキスト汚染のどれも、適切な設計で大幅に発生率を下げられます。耐障害性の高いマルチエージェントシステムを目指して、少しずつ改善を重ねてください。