ループエンジニアリングとグラフエンジニアリング

quarto-dsh のエージェントループ実装から読み解く、ループエンジニアリング/グラフエンジニアリング入門

概要

ループエンジニアリンググラフエンジニアリング は、AI エージェントを「都度プロンプトを打つ道具」ではなく「自律的に反復するシステム」として設計する実践です。本記事では、この2つの概念を整理し、quarto-dsh 自身のエージェントループ実装src/core/agent.ts など)を題材に「ループ」が実際にどう動くのかを読み解きます。

ループエンジニアリングとは

Google のエンジニア Addy Osmani 氏は、ループエンジニアリングを次のように定義しました。

Loop engineering is replacing yourself as the person who prompts the agent. You design the system that does it instead. (ループエンジニアリングとは、「エージェントにプロンプトを打つ人間」を自分からシステムに置き換えること。あなたは代わりにそれをやるシステムを設計する)

背景には、Claude Code の開発者 Boris Cherny 氏の言葉があります。

I don’t prompt Claude anymore. I have loops running that prompt Claude and figuring out what to do. My job is to write loops.

エージェント活用の抽象度は次のように段階を上げてきました。

階層 最適化の対象 典型的な問い
プロンプトエンジニアリング 1回の指示 どう書けば狙いどおり答えるか
コンテキストエンジニアリング 毎ターンの文脈 何をいつコンテキストに入れるか
ハーネスエンジニアリング 実行環境 エージェントが仕事を遂行・検証できる環境か
ループエンジニアリング 反復の制御構造 次の仕事をどう見つけ、どう検証し、いつ止めるか

下の層が不要になるわけではありません。プロンプトもコンテキストもハーネスも残ったまま、その上に自律的な反復を足すのがループエンジニアリングです。

Closed loop: 検証と停止条件を仕組みに移す

「テストが失敗しているので直したい」という場面を例にします。

従来(人間がループを回す):

❯ テストが失敗してるので直して        → 結果を確認
❯ まだ fizzbuzz(15) が失敗してる。直して → 結果を確認
❯ 直った。じゃあ lint も通して         → 結果を確認

結果の観測・検証・次の指示(ループの制御)を都度、人が担当します。

ループエンジニアリング:

❯ /goal ./test.sh が exit 0 で終わる。5ターン試してだめなら停止

検証や継続判断を仕組み(フック・スクリプト等)に移し、人間は停止条件を設計して、最後に結果をチェックする役に回ります。この「人間が担っていた反復の制御を仕組みに置き換える」ことがループエンジニアリングの本質です。

原始的なループ: Ralph ループ

Geoffrey Huntley 氏が紹介した原始的な形は bash のループです。

while :; do cat PROMPT.md | claude-code; done

同じプロンプトを毎回新しいコンテキストで流し込むだけですが、「状態を会話ではなくリポジトリ側(PROMPT.md・テスト)に持たせて反復する」という発想は、現在のループエンジニアリングに通じます。

グラフエンジニアリングとは

AI エージェント運用は「プロンプト → ループ → スワーム → グラフ」の順に進化するという 4 段階モデルがあります。グラフエンジニアリングはその到達点です。

段階 内容 限界
1. プロンプト 人がプロンプトを打ち、出力を読み、次を打つ(人間自身がループ) ラップトップを閉じれば何も残らない
2. ループ スクリプトで包みスケジュールで発火。状態を持ち自走する 「1 エージェント・1 ジョブ」に留まる
3. スワーム 役割ごとに多数のエージェントへ扇状展開 調整コスト・収束の担保が難しい
4. グラフ ノード(判断)とエッジ(データ受け渡し)で構造を宣言的に設計 設計の複雑さ

グラフエンジニアリングでは、ノード=エージェント(判断)、エッジ=データの受け渡しと捉え、「いつ並列に走り、いつ待ち、いつ再試行し、いつ検証するか」をグラフ自体が知っている状態を目指します。正典形は ダイヤモンド型(fan out → 処理 → 統合)です。

なお、段階が進んでも前の段階は不要になりません。LangChain 自身が「ループは単純なグラフにすぎず、ループエンジニアリングはグラフの代替ではなくその簡略版である」と述べており、グラフの各ノードは依然としてループです。

quarto-dsh のループ実装

quarto-dsh は「ハーネス」として、上記の 4 層モデルのうち ハーネス層 + ループ層 を実装しています。エージェントループは src/core/agent.tsrunAgent が担当します。

step/turn ループの構造

runAgent は「モデルへ問い合わせ → ツールを実行 → 結果を戻す」を最大 maxSteps(既定 8)まで繰り返します。

for (let step = 0; step < maxSteps; step++) {
  const { text, toolCalls } = await collectChat(ctx, options, messages, tools);
  messages.push({ role: 'assistant', content: text, toolCalls });
  ctx.sessions.append(sessionId, 'agent/step', { response: text, toolCalls });

  if (!toolCalls || toolCalls.length === 0) {
    return { answer: text, steps: step + 1 };          // ← 停止条件
  }
  for (const call of toolCalls) {
    const result = await ctx.tools.execute(call.name, call.arguments, tctx);
    ctx.sessions.append(sessionId, 'agent/tool', { name: call.name, ok: result.ok });
    messages.push({ role: 'tool', content: JSON.stringify(result), toolCallId: call.id });
  }
}
return { answer: 'step limit reached', steps: maxSteps }; // ← 上限で打ち切り

ポイントは「ループエンジニアリングの原則」と対応します。

  • 停止条件を設計するmaxSteps という機械的な上限と、ツール呼び出しが無くなったら終了する条件をコードで定義しています(agent.ts:66-68)。
  • 検証を本物にする — モデルの出力をそのまま受け入れず、quarto-lint / quarto-scan機械判定できる根拠で設定・記法を検証します。

検証者としてのツール

ループエンジニアリングで重要なのは「生成役と評価役の分離」です。quarto-dsh では、生成は LLM、検証はツールという役割分担になっています。

  • 生成: DeepSeek がコードや設定の修正案を生成
  • 検証: quarto-lint が実在キーで _quarto.yml を検証し、quarto-scan が callout 型・チャンクオプション・相互参照・Div の開閉を検査

システムプロンプトにも「不確かな設定キーは quarto-lint で検証してから提案してください」と明記し、自己採点に頼らない誘導をしています(src/app.ts:24-44)。

append-only セッションログ(ledger)

各 step・ツール実行は SessionLogappend-only で記録されます(src/core/session.ts)。

  • createSession() — セッション開始
  • append(sessionId, type, payload) — イベントを追記(agent/step / agent/tool
  • fork(source, boundary) — 途中から別セッションに分岐
  • ファイル指定時は JSONL に永続化され、再起動後も復元できます

これは DeepSeek Harness が説明する「append-only ledger(タスクの記録と fleet resume での再開)」と同じ発想です。セッションを途中でフォークできる点は、ループの「分岐」を支えます。

能力シームとイベント

ハーネス層として、quarto-dsh は Cordis 上に3つの能力シームを持ちます。

  • ctx.llm — モデル呼び出し(LlmService
  • ctx.shell — コマンド実行(ShellService
  • ctx.tools — ツール実行(ToolRegistry

ツール実行の前後には tools/pre-execute / tools/post-execute イベントが発火し(src/core/tools.ts:70-87)、「実行前チェック」「実行後フック」を挿入できます。これは Claude Code の PreToolUse / PostToolUse hook に相当し、実行環境を検証で固めるハーネス層の実装です。

グラフへの発展(今後の余地)

quarto-dsh の現状は「単一のループ」、つまり4段階モデルの ループ層 に留まります。グラフ層への発展余地は以下にあります。

  • fork による分岐SessionLog.fork() を使い、検証に失敗したセッションを分岐して並行試行する(fan out)
  • 検証ゲートtools/post-execute フックで lint / scan の結果をチェックし、不合格なら別ループを発火する(reduce / 統合)
  • 複数ループの編成 — 「生成ループ」「検証ループ」「公開ループ」をノードとして、エッジ(データ受け渡し)でつなぐ

ただし、段階を進める前に「その段階が必要か」を自己診断するのが正しい使い方です。単一ループで足りているうちは、グラフに飛びつく必要はありません。

まとめ

ループエンジニアリングの要点は、モデルの性能ではなく次の2点でした。

  • 検証を本物にする — テストや lint のような機械判定できる根拠で「完了」を判定する
  • 停止条件を設計する — 機械判定できる形で完了を定義し、上限とセットで運用する

quarto-dsh は maxSteps による停止条件と、lint / scan による機械的検証、append-only セッションログによる履歴という形で、この原則を実装しています。

Osmani 氏の言葉を最後に。

Build the loop. But build it like someone who intends to stay the engineer, not just the person who presses go. (ループを構築しましょう。ただし、単に「go ボタンを押す人」ではなく、エンジニアであり続ける意志を持つ人として構築しましょう)

ループが出力した成果物を読み、検証し、判断する責任は、これまでどおりエンジニアの側にあります。

関連