VS Code + Cline で ”Devin 風”開発を行う方法⑥ - マルチエージェントループ実行編 #2【設計見直し】

前回は 1 つのタスクについて、プラン作成からレビュー実行までの処理を実装しました。

今回はさらにレビュータスクに以下の機能を実装しました。

  • レビュー失敗時にプランを修正する機能
  • 新しいプランで再度タスクループを生成する機能
  • タスク完了時に次のタスクのループを作成して実行する機能

など

実際に何度か連続実行することはできましたが、継続して動作させてみると、Kanban 自体のクラッシュや、推論が途中で停止してしまうなどの問題が見えてきました。

Kanban 自体がクラッシュする問題については、PowerShell から再起動することである程度対応できそうです。しかし、推論が途中で停止した場合は Kanban ループ内で停止を検知して復旧することは困難です。

またセッション (タスク処理) ごとに処理がばらつくため、ワークフローに沿って動作しない場合もあるようです。

そのため、Kanban 内部でループ全体を制御する現在の方式は、今後さらに複雑化すると安定運用が難しくなると判断しました。

本記事では、今回の構成で実際に発生した問題と、ループ処理を Kanban の外側へ移す方針について整理します。

現在の進捗状況は以下の通りになっています。

状況項目
Planner (タスクループ生成 & 実行)
Worker
Reviewer
レポート生成
Fix Task 自動生成
Plan 修正
完全ループ

最終的に以下のような処理を目指しています。

今回検証を行った実行環境は以下のようになっています。

  • VS Code (Windows 版)・・・バージョン 1.131.0
  • Node.js (Windows 版)・・・バージョン 24.15.0
  • Cline・・・バージョン 3.0.48
  • Kanban・・・バージョン 0.1.70
  • Python・・・バージョン 3.14.6

※本記事でご紹介している内容は、確立された手法ではなく著者の独自の解釈に基づいています。その点をご理解のうえご参照ください。

本記事でご紹介したサンプルコードは記事末尾からダウンロードできます。

動作環境について

前回は、ローカル LLM として「qwen3.6:27b」というモデルを使用していましたが、VRAM 16GB の GPU では動作がかなり遅くなります。そこで今回は「qwen3.6:35b-a3b」というモデルに変更しました。パラメータ数が増えていますが、MoE (Mixture of Experts) 構成により、推論時にはモデル全体ではなく一部のパラメータだけが使用されます。私の環境では、27B モデルより実用的な速度で動作しました。

Ollama で使用する場合は、Windows や VS Code のターミナル画面で以下のように入力するとモデルがダウンロードされます。

ollama pull qwen3.6:35b-a3b
現在分かっている問題

開発を進めていくと、以下のような問題が発生することが分かりました。

  • Kanban アプリがクラッシュすることがある
  • 推論が途中で停止してしまう
  • セッション (タスク実行) ごとに処理がばらつく

など

Kanban アプリがクラッシュすることがある

特定の処理に時間がかかった場合、Kanban がクラッシュして終了するケースがあるようです。以下のようなエラーメッセージが表示されます。

Error: Unable to update lock within the stale threshold
    at file:///C:/Users/admin/AppData/Roaming/npm/node_modules/kanban/dist/cli.js:263033:17
    at callback (file:///C:/Users/admin/AppData/Roaming/npm/node_modules/kanban/dist/cli.js:261959:26)
    at FSReqCallback.oncomplete (node:fs:197:5) {
  code: 'ECOMPROMISED'
}

これは Kanban が保持しているロックファイルが一定時間以内に更新されなかった場合に発生するようです。具体的には、「Node.js のイベントループが長時間止まった」などの原因が考えられるようです。時間のかかるスクリプトの実行などが影響を与えていると思われます。

エラーメッセージは「cli.js」ファイルの 263033 行で表示されていますが、原因としてはこの関数の引数である「options.stale」という項目が関係していそうです。

options.stale には、ロックを正常に更新できない状態を許容する時間がミリ秒単位で設定されています。ただし、mtime の不一致やロックファイルの消失が検出された場合は、時間内でも ECOMPROMISED と判定されることがあるようです。

    function updateLock(file2, options) {
      const lock3 = locks[file2];
      if (lock3.updateTimeout) {
        return;
      }
      lock3.updateDelay = lock3.updateDelay || options.update;
      lock3.updateTimeout = setTimeout(() => {
        lock3.updateTimeout = null;
        options.fs.stat(lock3.lockfilePath, (err, stat10) => {
          const isOverThreshold = lock3.lastUpdate + options.stale < Date.now();
          if (err) {
            if (err.code === "ENOENT" || isOverThreshold) {
              return setLockAsCompromised(file2, lock3, Object.assign(err, { code: "ECOMPROMISED" }));
            }
            lock3.updateDelay = 1e3;
            return updateLock(file2, options);
          }
          const isMtimeOurs = lock3.mtime.getTime() === stat10.mtime.getTime();
          if (!isMtimeOurs) {
            return setLockAsCompromised(
              file2,
              lock3,
              Object.assign(
                new Error("Unable to update lock within the stale threshold"),
                { code: "ECOMPROMISED" }
              )
            );
          }

同じファイル内で「stale:」を設定している箇所が 4 箇所ありました。既定では 10 秒 (1e4) のようなので、30 秒 (3e4) に変更してみたところ、このエラーによる Kanban の終了は発生しにくくなりました。(今回は原因切り分けのために変更しており、恒久的な対策として推奨するものではありません。)

263074/9 - stale: 1e4,	C:\Users\admin\AppData\Roaming\npm\node_modules\kanban\dist\cli.js.org
263142/9 - stale: 1e4,	C:\Users\admin\AppData\Roaming\npm\node_modules\kanban\dist\cli.js.org
263286/5 - stale: request2.staleMs ?? DEFAULT_LOCK_STALE_MS,	C:\Users\admin\AppData\Roaming\npm\node_modules\kanban\dist\cli.js.org
263311/5 - DEFAULT_LOCK_STALE_MS = 1e4;	C:\Users\admin\AppData\Roaming\npm\node_modules\kanban\dist\cli.js.org

※ なお、node_modules/kanban/dist/cli.js はビルド済みのファイルです。変更前には必ずバックアップを作成してください。また、Kanban を更新または再インストールすると変更内容が失われる可能性があります。

しかしまだ以下のようなエラーが発生して、Kanban が終了する場合があるようです。

[Error: ENOENT: no such file or directory, stat 'C:\Users\admin\.cline\kanban\workspaces\index.json.lock'] {
  errno: -4058,
  code: 'ECOMPROMISED',
  syscall: 'stat',
  path: 'C:\\Users\\admin\\.cline\\kanban\\workspaces\\index.json.lock'
}

このエラーは、以下のような原因で発生する可能性があります。

  • Kanban が二重起動している
  • 別の Kanban 処理が同じロックを解除した
  • クリーンアップ処理が *.lock を削除した

など

そこで以下のようなスクリプトを作成して、Kanban 起動前にすでに起動している node.exe を停止することで、発生頻度を下げられる可能性があります。(実際に使用する場合は、対象プロセスのコマンドラインをより厳密に判定する必要があります。)

$LogDir = "kanban\logs"
$null = New-Item -ItemType Directory -Force -Path $LogDir

$LogFile = Join-Path $LogDir "cline-kanban.log"
$RestartCount = 0

function Write-KanbanLog {
    param([string]$Message)

    "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message" |
        Out-File $LogFile -Append -Encoding utf8
}

function Stop-StaleKanbanProcess {
    Get-CimInstance Win32_Process |
        Where-Object {
            $_.Name -eq "node.exe" -and
            $_.CommandLine -match "kanban"
        } |
        ForEach-Object {
            Write-KanbanLog "KILL STALE KANBAN PID=$($_.ProcessId)"
            Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
        }
}

while ($true) {
    $RestartCount++

    Stop-StaleKanbanProcess

    Write-KanbanLog "START #$RestartCount"

    cmd /c "npx kanban >> `"$LogFile`" 2>>&1"

    $ExitCode = $LASTEXITCODE

    Write-KanbanLog "EXIT CODE=$ExitCode"

    Start-Sleep -Seconds 5
}

exit 0
推論が途中で停止してしまう

時々、ワークフローの途中で推論が停止する場合があります。

この状態になると、コマンドで復帰させる方法がないので、プロンプト入力欄で処理を続行するように促すしかありません。またループ内のスクリプトから停止したことを検知するのは困難です。

外部ループであれば、定期的な Kanban コマンドの実行や、画面状態、ログ、ステータスファイルの監視などで「停止」したことを検知しやすくなります。しかしこの場合でも、コマンドで再開させる方法がないので現在の構成では、タスクを作り直して再実行する必要があると考えています。

Playwright などのツールによる画面状態の検知とログ解析などを組み合わせれば再開処理の実装なども可能かもしれません。

推論停止の原因は、Kanban だけでなく、ローカル LLM や Ollama、通信、ツール実行など複数の箇所に存在する可能性がありますが、より高性能なクラウド LLM も選択できる構成にすることで、モデル性能に起因する停止や手順逸脱を減らせる可能性があります。

セッション (タスク実行) ごとに処理がばらつく

新しくタスクを起動するごとにコンテキストや直前の実行状態が変わるので、ワークフローのような同じ動作を維持するのが非常に難しいです。

以下のような状態が発生します。

ツールの呼び出し方法を忘れる

前のセッションでは正しく実行できていたにもかかわらず、新しいタスクでは引数の形式を間違えたり、使用すべきコマンドではなく別のツールを呼び出したりすることがあります。

ワークフローの手順をスキップする

本来は結果ファイルを作成してからチェック処理を実行する必要がありますが、途中の手順を飛ばして終了処理へ進んでしまうことがあります。

完了済みの処理を再実行してしまう

すでに作成済みの結果ファイルを再度上書きしたり、完了済みのスクリプトをもう一度実行したりすることがあります。

改善のために行ったこと

処理のばらつきを抑えるため、Python や PowerShell のコマンドを dispatcher.py というスクリプトに集約し、各処理をサブコマンドとして統一しました。ワークフローでは、必要なコマンドが使用できるか確認するまで次の手順へ進まないようにし、全体を「初期化」「推論」「終了処理」の3段階へ簡略化しています。

また、AGENTS.md ファイルには必要なスキルや参照ファイルへのポインターだけを記載し、必要な情報を必要な時点で読み込む Lazy Load 方式を採用しました。各処理の実行状態も記録し、完了済みの処理が再度呼び出された場合は、開始時に処理を終了することで二重実行を防いでいます。

今後の構成

今後は、ループ制御を Kanban の外側へ移す予定です。現在のループとの違いは以下のようになります。

項目Kanban ループ外部ループ
LLM の呼び出しKanban 経由SDK または API 経由
推論を行う主体ローカルまたはクラウド LLMローカルまたはクラウド LLM
状態管理Kanban 内のワークフローとスクリプト外部プログラム
Git コミットKanban 内のエージェント処理またはスクリプト外部プログラム
停止検知困難タイムアウトや監視を実装可能
再試行ワークフロー依存条件と回数を明示的に制御可能
モデル切り替えKanban 側の対応範囲に依存プロバイダー切り替えを実装可能

Kanban には、Worker によるコード修正と Reviewer によるレビュー結果の作成など、LLM による推論が必要な処理だけを担当させます。

一方、以下の処理は外部ループで管理します。

  • タスクの開始と終了
  • 実行結果ファイルの検証
  • Git へのコミット
  • タイムアウトと再試行
  • Kanban プロセスの再起動
  • 次のタスクへの遷移

外部ループには、LangGraph や Microsoft Agent Framework などの利用も含めて検討しています。制御部分を外部化することで、ローカル LLM だけでなく、必要に応じてクラウド LLM へ切り替えられる構成も目指します。

今回実装した Kanban 内部のループ処理は最終構成には採用しない可能性がありますが、実際に動作させたことで、推論処理と制御処理を分離する必要性を確認できました。

ここまで作成したスクリプトは こちら からダウンロードできます。よろしければご覧ください。