Cline で作った Devin 風ワークフローを Microsoft Agent Framework で再構築する③ - Interrupt / Resume 実装編

前回は Microsoft Agent Framework (以下 MAF と表記します) の Workflow 機能を使用してグラフ構造の作成方法を確認しました。
今回は Request / Response という機能を使用して、LangGraph の Interrupt / Resume に相当する機能を実装していきます。
この記事で使用している実行環境は以下のようになっています。
- VS Code (Windows 版)・・・バージョン 1.141.2
- C# Dev Kit (Windows 版)・・・バージョン 3.40.210
- .NET SDK (Windows 版)・・・バージョン 10.0.402
Request / Response 機能のサンプル
こちら の公式ページにあるサンプルを見て仕組みを学習します。この機能は、エージェントループに人の介在 (Human-in-the-Loop) を行えるようにする仕組みですが、LangGraph の場合と同じように Kanban タスクの終了と同期させる用途にも使用可能です。
実際の処理はもう少し複雑ですが、概念的には以下のようになります。
- RequestPort を使用して Request / Response の情報をやり取りする
- RequestPort に Request を送信すると RequestInfoEvent が発生し、Workflow は Response 待ちになる
- SendResponseAsync で Response を返すと Workflow が再開し、後続の処理が実行される

動作確認のため、検証用のサンプルを作成してみます。
まずは何か適当なフォルダ (RequestResponseTest 等) を作成して、VS Code で開きます。VS Code のターミナル画面で以下のように入力して、サンプルプロジェクトのひな型を作成します。
dotnet new console続けて以下のコマンドを実行して、必要なパッケージをインストールします。
dotnet add package Microsoft.Agents.AI.Workflows --prereleaseまずは送受信するデータ構造を考えます。「Program.cs」ファイルを開いて以下のデータ構造を追加します。(実際に実行する場合は、Program.cs ファイルの「一番最後」にこの定義を追加しないとコンパイルエラーになります。)
// ----------------------------------------
// Request / Response のデータ型
// ----------------------------------------
public sealed record KanbanRequest(string TaskId);
public sealed record KanbanResult(
string TaskId,
bool Success,
string Message
);次に RequestPort と、結果を出力する Executor を含む Workflow を作成します。
RequestPort に Response を返すと Workflow 自体は再開しますが、このサンプルでは返された結果を確認しやすくするため、WorkflowOutputEvent を発生させる出力用 Executor を追加しています。
// ----------------------------------------
// Workflow を作成
// ----------------------------------------
// KanbanRequest を送り、KanbanResult を受け取る RequestPort
var kanbanPort =
RequestPort.Create<KanbanRequest, KanbanResult>("Kanban");
// WorkflowOutputEvent イベント発生用 Executor
var outputExecutor =
new FunctionExecutor<KanbanResult, KanbanResult>(
"KanbanResultOutput",
(result, _, _) => result
);
// 実際には Planner や Executor から
// この RequestPort に Request を送る Workflow を構築する想定
Workflow workflow = new WorkflowBuilder(kanbanPort)
.AddEdge(kanbanPort, outputExecutor)
.WithOutputFrom(outputExecutor)
.Build();最初のグラフで表現すると、以下のようになります。

作成した Workflow を実行するコマンドを追加します。同時に KanbanRequest データを引数で渡します。
// ----------------------------------------
// Workflow 開始
// ----------------------------------------
await using StreamingRun run =
await InProcessExecution.RunStreamingAsync(
workflow,
new KanbanRequest("T0001")
);Request 発生時と Workflow 完了時の処理を行うイベントハンドラを追加します。
送信イベント (RequestInfoEvent) の場合はダミーのタスク監視メソッド「WaitForKanbanAsync」を呼び出しています。Workflow 完了時 (WorkflowOutputEvent) は「Workflow 完了: KanbanResult {...」と表示します。
// ----------------------------------------
// Workflow のイベントを監視
// ----------------------------------------
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
switch (evt)
{
// Workflow が外部応答待ちになった
case RequestInfoEvent requestEvent:
{
Console.WriteLine("Kanban の完了待ちに入りました。");
// --------------------------------
// KanbanRequest データを取得
// --------------------------------
if (!requestEvent.Request.TryGetDataAs<KanbanRequest>(
out KanbanRequest? kanbanRequest) ||
kanbanRequest is null)
{
throw new InvalidOperationException(
"KanbanRequest データを取得できませんでした。"
);
}
// --------------------------------
// ここで Kanban を監視
// --------------------------------
KanbanResult result =
await WaitForKanbanAsync(kanbanRequest.TaskId);
Console.WriteLine("Kanban が完了しました。");
// --------------------------------
// Workflow に Response を返す
// --------------------------------
var response =
requestEvent.Request.CreateResponse(result);
await run.SendResponseAsync(response);
break;
}
// Workflow 全体が完了
case WorkflowOutputEvent outputEvent:
{
Console.WriteLine(
$"Workflow 完了: {outputEvent.Data}"
);
return;
}
}
}ダミーの監視関数を追加します。今回は動作確認のため、3秒待機したあと強制的に完了扱いにしています。実際の Kanban 連携では、この部分をタスクのステータスを確認する処理に置き換えます。
// ----------------------------------------
// Kanban 監視処理
// ----------------------------------------
static async Task<KanbanResult> WaitForKanbanAsync(string taskId)
{
while (true)
{
Console.WriteLine($"{taskId} を監視中...");
// 本番では
// var status = ReadKanbanStatus(taskId);
// のような処理になる。
await Task.Delay(TimeSpan.FromSeconds(3));
// サンプルなので Done になったことにする
bool done = true;
if (done)
{
return new KanbanResult(
taskId,
true,
"Kanban task completed"
);
}
}
}以上でサンプルの作成は終了です。
実際に実行してみると、RequestInfoEvent の発生後に Kanban の監視処理へ入り、SendResponseAsync で結果を返すと Workflow が再開して WorkflowOutputEvent が発生することを確認できました。

ここまでのサンプルコードをまとめたものは、以下のようになっています。
短いサンプルでしたが、前回と今回の機能を合わせると以下に示した Kanban ループの青い点線で囲んだ部分を置き換えることができます。

MAF には Workflow の状態を保存して後から再開する Checkpoint 機能もあります。ただし今回の用途では、Kanban タスクの実行中もアプリケーション自体は起動したままとするため、Checkpoint は使用せず Request / Response による待機だけで実装します。
アプリを終了せずに待機できるため、デバッグもしやすくなります。
(※ LangGraph でも、アプリを終了せずに async / await などを使用して同様の構成にすることが可能です。ただし、その場合でも DB やメモリを使用してチェックポイントを保存する必要がある点が MAF と異なっています。)
まとめ
今回は Microsoft Agent Framework の Request / Response 機能を使用して、LangGraph の Interrupt / Resume に相当する処理を実装しました。
RequestPort から Request を送信すると Workflow を一時的に Response 待ちの状態にでき、外部処理の完了後に SendResponseAsync で Response を返すことで、停止していた Workflow を再開できることを確認しました。
今回作成したサンプルでは、Kanban タスクの完了待ちをダミー処理で実装しましたが、実際の開発ではこの部分を Kanban のステータス監視処理に置き換え、タスク生成処理と組み合わせることで、MAF から Kanban タスクを起動し、処理完了後に Workflow を再開する仕組みを構築できます。
また、MAF には Checkpoint を使用して Workflow の状態を保存・復元する機能もありますが、今回のようにアプリケーションを起動したまま外部処理の完了を待つ用途では、Request / Response のみでも比較的シンプルに実装できます。
次回は、今回作成した仕組みを実際の Kanban ループに組み込み、MAF からタスクを起動して処理結果を受け取りながら Workflow を進める部分を実装していきます。
今回は以上です。

