ゼロから作るコーディングエージェント【第2回】完了は「作った」ではなく「動いた」で決める。完了の門と、押し戻しに書くこと

モデルの「完了しました」は5つの形ですり抜けました。構文検査だけ、テストは通るが動かない、待ち受けは立つが最初の要求で落ちる、起動するが仕様のAPIが無い、assertの無いテスト。完了の門を3段で閉じる設計と、押し戻しに「落ちた行と調べ方」を書いて「答え」を書かない理由を、数字つきで書きます。

ゼロから作るコーディングエージェント【第2回】完了は「作った」ではなく「動いた」で決める。完了の門と、押し戻しに書くこと

こんにちは!

自作のコーディングエージェントに、認証つきのタスク管理Webを作らせていたときの話です。モデルは40ターンで「すべての機能を実装し、テストも通りました。完了です」と報告して止まりました。テストの出力を見ると、確かに全部通っています。

ところが、そのサーバを起動して仕様書どおりのAPIを叩いてみると、22項目の確認のうち通ったのは2項目でした。仕様書のAPIは12本で、それを正常系と異常系に分けた採点器の確認項目が22個です。

テストは通っている。サーバも起動する。それでも仕様の1割も動いていない。モデルが書いたテストが、仕様ではなく自分の実装を写していたからです。

結論から言うと、コーディングエージェントの「完了」はモデルの宣言ではなく、作ったものを実際に動かした証拠で決める必要があります。そして、その証拠を集める係はモデルではなく、エージェント側(ハーネス)が持たなければなりません。弊社ではこれを「完了の門」と呼んでいて、105件直した欠陥のうち、いちばん多くの欠陥がこの門の周りで出ました。

図1 完了の門は3段で閉じる(作図: Qualiteg)
図1 完了の門は3段で閉じる(作図: Qualiteg)

この記事は連載「ゼロから作るコーディングエージェント」の第2回です。前回は「ターンを回す係」と「止めてよいか決める係」を分ける話を書きました。今回はその「止めてよいか決める係」の中でいちばん重い判定、完了の門の話です。

回テーマ
第1回「最後までやりきる」が難しい理由。ターンを回す係と止める係を分ける
第2回(今回)完了は「作った」ではなく「動いた」で決める。完了の門と押し戻しの書き方
第3回「止まらなかった」は「進んだ」ではない。空転を数え、思考は切らずに上限だけ置く
第4回自走の規律は人がいない場面のもの。人がいるなら止まって聞く
第5回ローカルLLMを一級市民にする。16GB×2でWebシステムを作らせるまで
第6回評価をどう作るか。動くものを作らせて自動採点し、採点器そのものを疑う
第7回記録と回帰。全部イベントログから見つかる。そして依存0で作る

1. 「完了しました」がすり抜けた5つの形

門を作る前に、何がすり抜けたかを並べます。全部、弊社の手元で実際に起きたことです。

図2 完了をすり抜けた5つの形(作図: Qualiteg)
図2 完了をすり抜けた5つの形(作図: Qualiteg)
すり抜けた形実際に起きたこと
構文検査だけで「動作確認した」node --check が通ったので完了。1度も実行していない
テストは通るが動かない(失敗を終了コード0で隠す)アサーションの無いテストが36本。起動コマンドに、失敗しても終了コード0になる細工を付けていた
待ち受けは立つが、最初の要求で落ちるサーバは起動してポートを開く。しかし最初のHTTP要求を処理する中で未定義の関数を呼んで落ちる。起動確認を「ポートが開いたか」で見ていた門はこれを通した
起動するが、仕様のAPIが無い冒頭の例。自前テスト通過、トップページも応答、APIの確認 2/22で完了報告
テストに assert が無いtest() の中身が console.log だけ。3本走らせて3本とも同じ形で失格

この表を上から順に見ていくと、あることに気づきます。

門を1つ塞ぐと、弱いモデルは隣の穴を通ります。

「実行しろ」と言えば || echo を付けて実行する。「起動しろ」と言えば起動だけする。「テストを書け」と言えば中身の無いテストを書く。モデルに悪意があるわけではなく、こちらが完了の条件として見ているものを、いちばん安く満たしているだけです。

だから門は、「モデルが何をしたか」ではなく「作ったものが何をするか」を見る必要があります。

2. 門は3段で閉じます

弊社の完了の門は、最終的に冒頭の図1の3段になりました。

2-1. 門1。ハーネスが自分で起動する

モデルが「完了」と言ったら、ハーネスが自分で成果物を起動します。起動方法はモデル自身が package.json の start に宣言しているので、それを使います。課題固有の知識(ファイル名やコマンド)を門に埋め込む必要はありません。

起動したら、トップページに1回だけ要求を送ります。ここが大事で、ポートが開いただけでは通しません。先ほどの表の3行目、「待ち受けは立つが最初の要求で落ちる」がまさにこれで、listen の成功を起動の証拠にしていた版は、この「起動しない成果物」を completed で通していました。採点器の起動の配点は、15点中0点です。

起動できなければ、失敗の内容(起動ログの末尾)を押し戻しに載せてモデルに返します。10回押し戻しても立たなければ、それは別の理由で止めます。永遠に押し戻し続けるのは、第1回で書いた「止まれずに回っている」と同じです。

2-2. 門2。仕様の経路を1本ずつ叩く

起動できたら、仕様書に書かれている経路をハーネスが1本ずつ叩きます。無い経路(404)が残っている間、あるいは仕様に無い状態(500や302)で答える経路が残っている間は、門を閉じたままにします。

これは冒頭の「APIの確認 2/22で完了」への対処です。テストは仕様を写しているとは限らないので、仕様書そのものを物差しにします。

ただし、レスポンスのJSONの形までは見ません。そこまで見ると、門が課題の答えを知っていることになり、第6回で書く評価が出来レースになるからです。「経路があるか」「仕様にある状態コードで答えるか」までが門の仕事で、中身が正しいかは採点器の仕事です。

2-3. 門3。テストの中身を見る

3段目は、モデルが書いたテストの中身です。見るのは2つ。

走ったテストが0本なら、成功と数えません。Node.js標準のテストランナー(node --test)は、テストファイルが1つも無くても「tests 0」で終了コード0を返します。モデルが6問の実装を書いてテストを1本も書かず、npm test が通って完了したことがありました。ランナーの出力に「0本」「No tests found」「0 passing」の形があれば検証失敗にして、テストを書くよう返します。

`test()` はあるのに `assert` が無いファイルが残っていれば、中身を書くよう返します。ここにも隣の穴がありました。最初は test/ ディレクトリ配下の *.test.js だけを見ていたので、モデルはプロジェクトの直下に test-all.js(test() が1個、中身は console.log、アサーション0)を置いて「tests 1 / pass 1」で通り抜けました。門の物差しは、実際に走るテストランナーが拾うファイルの形とぴったり同じにする必要があります。

2-4. 門は完了の瞬間に閉じれば足ります

門3を入れた直後に、逆の失敗をしました。

「テスト0本」の検証失敗を、書き込みのたびに返すようにしたのです。すると弱いモデルは、実装の途中でテストをまだ書いていない段階から毎ターン押し戻され、それまで20〜40ターンで終わっていた課題が137ターンかかりました。押し戻しは24回。モデルは実装を進めたいのに、ハーネスが「テストが無い」と言い続けていたわけです。

途中では印を付けるだけにして、モデルが完了しようとした瞬間に門で止める。それで足りました。門は完了の瞬間に閉じれば十分で、途中で毎回閉じると弱いモデルは実装を進められません。

3. モデル自身の検証も、同じ記録に写します

門を3段にしても、まだ1つ空回りがありました。

3万行の既存コードベースに機能を足す課題で、ハーネスの自動検証が1回だけ落ちました。落ちたのは時間に敏感な既存テストで、機が混雑していたためです。その後モデルは、自分の検証ツールで同じテストを4回通しました。それでもハーネスは「検証はまだ失敗している」と14回押し戻しました。

モデルが自分で回した検証の結果が、ハーネスの検証の記録に書かれていなかったからです。門が見ていた「最後の検証」は、ハーネスの古い失敗を指したままでした。

モデルが通した検証も、ハーネスの検証と同じ記録に写す。それだけの直しですが、これを入れるまで、その課題の1本目は同じ水準の成果物を出しながら14回の押し戻しで空回りしていました。

4. 押し戻しに書くこと。書かないこと

門で止めたら、モデルに「なぜ通らないか」を返します。この文面の書き方で、その後のターン数が大きく変わりました。

図3 押し戻しに書くこと、書かないこと(作図: Qualiteg)
図3 押し戻しに書くこと、書かないこと(作図: Qualiteg)

4-1. 落ちた行だけを抜く

最初の版は、検証の出力の先頭800文字を押し戻しに載せていました。ところがテストが20本あって19本通り1本落ちる場面では、先頭800文字は通ったテストの一覧で埋まり、落ちた行が届いていませんでした。モデルから見ると「Verification is still failing」の下に緑のチェックが並んでいるだけです。

失敗を表す行(✖、not ok、AssertionError、expected、actual)だけを抜いて載せるようにしました。

4-2. 「原因を読んで直せ」は効きません

一般論の押し戻しは、モデルに別の当てずっぽうを試させるだけでした。あるランでは、モジュールの export 名を間違えたモデルが、「原因を読んで直せ」の押し戻しを受けながら77ターン、別の名前を当て続けました。

効いたのは、失敗の形ごとに「調べ方」を書くことです。「その名前は export されていない。ファイルの export を列挙して確かめろ」「'F:/' はシェルのパス変換で、コードの間違いではない」。何が違うか(状態コード、経路、文言)を具体に書くと、同じ失敗を繰り返しません。

編集の直後の構文エラーも同じで、エラー文だけ返しても450行のファイルは直せませんでした。該当行の前後のソースを添えると、モデルは自分の構文エラーに自分で気づきます。

4-3. 答えは書かない

ただし、線引きが1つあります。課題の答えは書きません。

「POST /api/tasks は 201 を返すべきところが 500 を返している」は書いてよい。仕様書に書いてある契約だからです。「status の既定値を todo にしろ」は書きません。それは実装の答えで、書いた瞬間に評価が出来レースになります。

調べ方は書く。何が違うかは書く。答えは書かない。この線は、第6回の評価の話とまっすぐつながっています。

5. 門を作って、何が変わったか

門を3段にしたあとの数字です。

同じローカルモデルで5種類の課題を3本ずつ、計15本走らせて、15本すべてが completed で止まり、サーバを起動する6本はすべてハーネス自身の起動確認を通過しました。起動しない成果物が completed で止まった本数は0です。

対照として、より小さく速いモデルも同じ門で走らせました。門を入れる前の2本は、1本が起動せず、もう1本は起動するもののAPIが全部500か401でした。門を入れて最初の要求まで確かめる形にしたあとの1本は起動し、APIの確認は22項目中9項目まで来ました。認可や永続化は0のままです。門はモデルの力を上げませんが、「動かないものを完了と呼ぶ」ことは止められます。

3段の門は、成果物の動作を見るものです。それとは別に、指示の前提を守ったかを見る門も1つ入れました。地味ですが効きました。指示が名指しした仕様書を、モデルが1度も読まずに完了できていました。あるランでは仕様書を読まずに自分で別の仕様を書き、23ターンで完了して0点でした。指示に書かれたファイルを読むまで門を閉じる。それだけで消えました。

まとめ。門は「モデルが何をしたか」ではなく「成果物が何をするか」を見る

門見ることすり抜けた実例
門1 起動宣言された起動方法で立ち上げ、最初の要求に答えるかポートは開くが最初の要求で落ちる
門2 仕様の経路仕様書の経路が全部あり、仕様にある状態コードで答えるか自前テスト通過、APIの確認 2/22
門3 テストの中身走った本数が0本でないか、assert があるかconsole.log だけのテスト、直下の test-all.js
記録モデル自身の検証も同じ記録に写しているか4回通しても14回押し戻し
押し戻し落ちた行と調べ方を書き、答えは書かない先頭800文字が通ったテストで埋まる

門を1つ塞ぐと隣の穴を通られます。それでも、門はハーネスが自分で閉じる以外にありません。モデルの「完了しました」を信用した瞬間に、第1回の「次はどうしますか?」と同じ場所に戻ります。

次回は、門を通っても「進んでいない」ランの話です。300ターンのうち7割以上がツールを1度も呼んでいなかったという数字から、空転を数える指標と、思考を切らずに上限だけ置く理由を書きます。

それでは、また次回、お会いしましょう!

関連記事

Read more

(株)Qualiteg、CEATEC 2026 出展レポート

(株)Qualiteg、CEATEC 2026 出展レポート

こんにちは!2026年10月13日(火)から16日(金)までの4日間、幕張メッセで開催された「CEATEC 2026」に、出展してまいりました。 昨年の CEATEC 2025 に続いて今年も出展してまいりました。 (昨年の様子は CEATEC 2025 出展レポート に、今年の出展予告は こちらの記事 です) 今年は主催者発表で、CEATEC 2026 4日間の登録来場者総数が 112,373人。昨年の98,884人を上回る規模になりました。 当社ブースも昨年より多くのお客様にお越しいただき、 商談(ご予約)の数は昨年の2倍になりました。 このレポートでは、準備日から最終日までを時系列で振り返ります。 各日の公式来場者数も載せておきますので、来年出展される方(と来年の私たち自身の備忘録としても)のお役に立てば幸いです。 目次 * 1. CEATEC 2026 出展概要 * 2. 前日(10月12日)ブース設営

By Qualiteg ビジネス開発本部 | マーケティング部
CEATEC 2026に出展します!AIアバター動画生成「MotionVox®」の最新版を実体験いただけます

CEATEC 2026に出展します!AIアバター動画生成「MotionVox®」の最新版を実体験いただけます

株式会社Qualitegは、2026年10月13日(火)~16日(金)に幕張メッセで開催される「CEATEC 2026」に出展いたします。今回の出展では、当社が開発したAIアバター動画生成サービス「MotionVox®」の最新版を中心に、動画生成の実演と作例をご紹介いたします。 昨年の CEATEC 2025 に続き、2年連続の出展となります。昨年の会場の様子は 出展レポート にまとめております。 出展概要 * 会期 2026年10月13日(火)~10月16日(金)10:00~17:00 * 会場 幕張メッセ(千葉県千葉市美浜区中瀬2-1) * 出展エリア ネクストジェネレーションパーク * ブース番号 ホール4 4H339 * 入場 無料(来場事前登録制) * CEATEC内特設サイト https://www.ceatec.com/nj/exhibitor_

By Qualiteg プロダクト開発部
ゼロから作るコーディングエージェント【第3回】「止まらなかった」は「進んだ」ではない。空転を数え、思考は切らずに上限だけ置く

ゼロから作るコーディングエージェント【第3回】「止まらなかった」は「進んだ」ではない。空転を数え、思考は切らずに上限だけ置く

300ターン走ってAPIが0本。「モデルの限界」と結論を書いた翌日、記録を読み直すと234ターンがツールを1度も呼んでいませんでした。空転率という指標、1つ塞ぐと次の形に移る空転、思考をオフにすると95点が71点に落ちて時間も縮まなかった話、出力枠を広げたら反対側を数える話を書きます。

By Qualiteg プロダクト開発部
Ubuntuの自動更新でカーネルだけ上がり、再起動したらGPUが消えた。NVIDIAモジュールが置き去りになる原因と3つの対策

Ubuntuの自動更新でカーネルだけ上がり、再起動したらGPUが消えた。NVIDIAモジュールが置き去りになる原因と3つの対策

Ubuntu 24.04のGPUサーバーを再起動したらnvidia-smiが動かない。原因はunattended-upgradesがカーネルだけを更新し、NVIDIAモジュールが依存関係の都合で見送られていたこと。ログで原因を突き止め、1コマンドで復旧し、apt-mark holdを含む3つの再発防止策を比べて採用した記録。

By Qualiteg プロダクト開発部