前書き
生成AI界隈の移り変わりの速さには、毎回驚かされます![]()
先月あたりはSNS上で「ループエンジニアリング」という言葉が盛り上がっていたと思ったら、もう次の言葉に注目が移っていました。
それが「グラフエンジニアリング」です![]()
きっかけは2026年7月18日、Peter SteinbergerさんがXで「まだループの話をしているのか、もうグラフの話に移ったのか」という趣旨の投稿をしたことでした。
あっという間に言葉が独り歩きを始めます。
この記事では、グラフエンジニアリングという言葉が指す中身と、Claude Codeの「ダイナミックワークフロー」を使った実装例までを紹介します。
グラフエンジニアリングの定義と構成要素
一言でまとめると、グラフエンジニアリングは複数のエージェントループをノード・エッジ・ステートでつなぎ、実行順序や並列性を設計する手法のことです。
正直にいうと、中身自体はまったく新しい技術ではありません![]()
ループとの関係
グラフエンジニアリングを説明する前に、まずループを軽く紹介します。
ループは 「1つのエージェントが目標に向かって回す自律サイクル」 です。
ループエンジニアリングに詳しくない方は下記の記事を参考にしてください
対してグラフは 「多くのループにまたがる調整レイヤー」 です。
言い換えると、単一のエージェントループも、自分自身を指すエッジを持つ1ノードのグラフと見なせます。
つまりグラフはループを置き換えるものではなく、ループ同士を接続して管理するものです。
技術的な仕組み自体は、状態遷移図(ステートマシン)の応用で目新しくありません。
ただ「単一ループから、共有状態を持つ特化ノード群へ」という設計上の移行が実務で実際に起きている、という点は擁護できると思います。呼び名がなかった課題に、名前がついただけとも言えます。
構成要素
実装の細部はフレームワークやツールによって異なりますが、共通しているのは ノード・エッジ・ステート の3要素です。
ノードは作業の単位です。1つのエージェント呼び出し、決定論的な関数、ツール呼び出し、人間の承認などが該当します。「1つの入力、1つの出力、1つの仕事」という契約を持たせるのが基本です。
エッジは次に何を実行するかを決める依存関係です。「次のステップは、前のステップの出力を本当に読んでいるか?」に答えが"No"なら、それは独立したノードであり並列化できる、という判定方法もあります。
ステートは、エッジに沿って流れる共有オブジェクトです。リサーチ結果や下書き、レビューの判定結果など、各ノードが残した情報がここにまとまり、すべてのノードがここを読み書きします。
この3要素に加えて、実装時によく登場する部品や、それらを組み合わせた定型パターンも整理しておきます。
特に 「ファンアウト・リデュース・シンセサイズ」 という定型パターンは、実務のグラフの多くが最終的にこの形に帰着すると言われるほど頻出です。
| 部品 | 役割 |
|---|---|
| ルーターノード | 「合格なら次へ、不合格なら差し戻す」のような分岐判断を、モデルではなくif文などの決定論的なコードにやらせる部品です。 |
| ファンアウト・リデュース・シンセサイズ | 1つの仕事を複数ノードに分割して並列実行し(ファンアウト)、その結果の重複排除や集計はエージェントを使わずコード側で済ませ(リデュース)、最後に1つのノードが全体をまとめて答えを書き上げる(シンセサイズ)という3段構成です。 |
| バリア | 並列に走らせたノードの結果を1箇所に集約する直前に置く同期ポイントです。集約自体は1つのノードですが、バリアが指すのはその手前で「全ノードの完了を待つ」という振る舞いのことです。途中で失敗したノードが1つあってもそこだけ空扱いにして弾き、バッチ全体は止めずに進めます。 |
| 検証者ノード | 作業したノードとは別に立てる、結果を積極的に疑ってかかる専用ノードです。ワーカーと同じ会話履歴(コンテキスト)をそのまま渡すと、話の流れに引きずられてなあなあで同意し合うだけになりがちなので、まっさらなコンテキストや別のモデルで判定させるのがポイントです。 |
| 隔離(Isolation) | 各エージェントを別々のワークツリーやサンドボックスで動かす部品です。プロンプト上には現れない「隠れたエッジ」、たとえば複数のエージェントが同じファイルに同時書き込みしてしまう競合を防ぎます。 |
グラフエンジニアリングの向き不向き
ほとんどの場合、グラフはオーバースペックです![]()
グラフが要るのは下表の条件に当てはまったときだけだと考えてください。
| 兆候 | ループで十分な場合 | グラフが必要な場合 |
|---|---|---|
| タスクの形状 | 明確なゴールが1つ、または探索的でゴールがまだ定まっていない | 明確な専門分野に分かれ、並行での分散と結合が要る |
| 使用するツール・モデル | 全体を通して同じツール・モデルでこなせる | ステップごとに違うモデルやツールを割り当てたい |
| 制御フロー | 各ステップを人が逐一承認したい、またはエージェントに自由を与えても安全 | 障害の分離と、監査できるルーティングが要る |
また、グラフを採用する場合、ノードを増やすほどコストはかけ算で膨らみます。単一の会話より多くのトークンを使う前提で見積もっておくのが安全です。
Claude Codeでの実装
ここまでは概念の話でした。
向き不向きが分かったところで、実際にClaude Codeを使ってグラフを組んでみます![]()
ダイナミックワークフロー
Claude Code上でグラフエンジニアリングを使う入り口が「ダイナミックワークフロー」です。ノード・エッジ・ステートの配線を自分で書く代わりに、Claudeに書かせる アプローチです。
Claude Codeで多数のサブエージェントを使うタスク——たとえば数十ファイルを一斉に監査する、といった作業——を素の会話でやろうとすると、各ステップの指示や結果がすべてClaudeの会話コンテキストに積み上がっていきます。動かすエージェントが増えるほど会話は肥大化し、Claudeが保持しておくべき情報も増え続けます。ダイナミックワークフローは、この 「規模が大きくなるほど会話が重くなる」 という問題を解決するための機能です。
実体は、多数のサブエージェントを操るJavaScriptスクリプトをClaudeに書かせ、会話から切り離してバックグラウンド実行する仕組みです。ループや分岐、中間結果の保持はスクリプトが担うので、会話コンテキストには最終結果だけが残ります。
向いているのは、次のようなタスクです。
- 「1つの会話でさばける規模を超えるタスク」— コードベース全体のバグ調査、数百ファイル規模のマイグレーション、複数の情報源を相互検証する調査タスクなど
- 「同じオーケストレーションを繰り返し実行したいタスク」
Claude Codeにはもともとサブエージェント・スキル・エージェントチームという仕組みがありますが、違いは「次に何をするかの計画を誰が持つか」です。
| 仕組み | 計画を持つ主体 | 中間結果の置き場所 |
|---|---|---|
| サブエージェント | Claudeがターンごとに判断 | Claudeのコンテキストウィンドウ |
| スキル | Claudeが指示に従う | Claudeのコンテキストウィンドウ |
| エージェントチーム | リードエージェントがターンごとに判断 | 共有タスクリスト |
| ダイナミックワークフロー | スクリプトが判断 | スクリプト内の変数 |
起動は、プロンプトに ultracode というキーワードを含めるか、「ワークフローを使って」 のように自然言語で頼むだけです。
とにかく動きを見てみたいだけなら、Claude Codeに標準搭載されている /deep-research <調べたい質問> を実行するのが一番手っ取り早く、スクリプトを1行も書かずにワークフローの挙動を体験できます。
ワークフローの途中経過は/workflowsで確認できます。
実行中・完了済みのワークフローをフェーズ単位のエージェント数やトークン消費量つきで確認でき、pキーで一時停止・再開、xキーで停止といった操作もできます。
スクリプトの中身は、agent() で1つのサブエージェントを呼び出し、pipeline() でアイテムごとに独立して流していく、という組み合わせがベースになっています。
試しに、複数の主張を独立したエージェントでファクトチェックするだけの小さな例を書くとこうなります。
export const meta = {
name: 'fact-check-claims',
description: '複数の主張を独立したエージェントでファクトチェックする',
phases: [{ title: 'Check' }],
}
const claims = [
'LangGraphは2024年1月にリリースされた',
'グラフはループを置き換える概念だ',
]
const results = await pipeline(
claims,
(_prev, claim) =>
agent(`次の主張の真偽を根拠つきで判定してください: ${claim}`, {
phase: 'Check',
schema: {
type: 'object',
properties: {
verdict: { type: 'string' },
reason: { type: 'string' },
},
required: ['verdict', 'reason'],
},
})
)
return results
このスクリプトを.claude/workflows/fact-check-workflow.jsとして保存すれば、meta.nameの値がそのままコマンド名になり、以後は/fact-check-claimsのようにスラッシュコマンドとして起動できます。
正式には、その場でClaudeに一度実行させたワークフローを/workflowsからsキーで保存する流れが推奨されています。
このスクリプトを組み立てる作業自体が、前章で説明したノードとエッジの設計そのものだということです。agent() の1呼び出しがノード、pipeline() に渡す関数の入出力がエッジ、戻り値の schema がノードの契約にあたります。
隔離(Isolation)も同じ発想で書けます: ファイルごとに競合が起きうる編集作業を並列で行いたい場合は、エージェントごとに独立したワークツリー上で作業させてから結果をマージする、という組み方もできます。これもグラフでいう隔離(Isolation)そのものです。
グラフエンジニアリングという言葉を知らなくても、この機能を触ればほぼ同じ発想にたどり着く作りになっています![]()
多くのエージェントを並列に動かす分、単一の会話で同じ作業をするより消費トークンが増える可能性があります。
- 同時実行数: 最大16エージェント(CPUコアが少ない環境ではさらに少なくなります)
- 1回の実行あたりの上限: 合計1,000エージェント
-
警告表示の閾値: 25個を超えるエージェントをスケジュールする、または見積もりトークン合計が150万を超えると、画面に
Large workflow警告が表示される -
事前設定:
/configの Dynamic workflow size でエージェント数の目安をあらかじめ設定しておくと、実行規模を抑えやすい
ノード数が多い、または見積もりトークン量が大きいタスクほど、最初は小さいタスクで試すのが安全です。
その他フレームワークでの実装
グラフエンジニアリングという言葉が生まれる前から、同じ発想を実装しているフレームワークはいくつかあります。2つそれぞれの最小構成のコードも載せておきます![]()
LangGraph
StateGraph にノードを登録し、add_edge / add_conditional_edges で経路を配線するスタイルです。共有状態は TypedDict で型を決めた1つのオブジェクトにまとまります。
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict): # 共有状態: 全ノードがここを読み書きする
draft: str
approved: bool
def write(state: State) -> dict: # ノード
return {"draft": "生成した下書き"}
def review(state: State) -> dict: # ノード
return {"approved": True}
def route(state: State) -> Literal["write", END]: # エッジの行き先を決める判定
return END if state["approved"] else "write"
builder = StateGraph(State)
builder.add_node("write", write)
builder.add_node("review", review)
builder.add_edge(START, "write")
builder.add_edge("write", "review") # エッジ: write → review
builder.add_conditional_edges("review", route) # 条件付きエッジ: 不合格なら write に戻す
graph = builder.compile()
graph.invoke({"draft": "", "approved": False})
add_node に渡した1関数がノード、add_edge / add_conditional_edges が結ぶ経路がエッジ、State という1つの TypedDict が共有状態、という対応関係になります。
Google ADK
SequentialAgent に sub_agents を並べ、各エージェントの output_key で書き込んだ値を、次のエージェントの instruction 内で {変数名} として読み込む形です。
from google.adk.agents.sequential_agent import SequentialAgent
from google.adk.agents.llm_agent import LlmAgent
model = "gemini-3.6-flash"
writer = LlmAgent( # ノード
name="Writer", model=model,
instruction="Pythonコードを書いてください。",
output_key="generated_code", # ステートへの書き込み
)
reviewer = LlmAgent( # ノード
name="Reviewer", model=model,
instruction="次のコードをレビューしてください: {generated_code}", # エッジ: 前の出力を読む
output_key="review_comments",
)
pipeline = SequentialAgent(name="Pipeline", sub_agents=[writer, reviewer])
sub_agents に並べた各 LlmAgent がノード、{generated_code} のようなテンプレート変数を介した受け渡しがエッジ、その受け渡し先であるセッション状態(output_key の書き込み先)が共有状態です。
どのフレームワークも、ノード・エッジ・共有状態という組み合わせ自体は共通しています。違いは、その組み合わせをコードで書くのか(LangGraphやADK、AutoGen)、Claudeに書かせるのか(ダイナミックワークフロー)というところで、後者は自分でグラフの配線を1本ずつ書かなくていい分、入門のハードルは低く感じました。
最後に
グラフエンジニアリングの実態は、数十年前からあるステートマシンの応用に、2026年7月のX上でたまたま名前がついたものです![]()
ただ、名前がバズワード的に広まったからといって、中身まで軽く見るのはもったいないとも思います。ループが1つでは調整しきれなくなったときにどう分割するか、検証者をどう独立させるか、という設計の勘所自体は、この言葉を知らなくても遅かれ早かれ突き当たる問題だからです。
次に「〇〇エンジニアリング」という言葉がタイムラインに流れてきたら、名前に身構えずに「これは結局、何のノードと何のエッジの話をしているんだろう?」と1歩引いて分解してみることをおすすめします。
参考資料







