VacantAI のアカウンタビリティ層GitHub

 · 記録と多者実行 · 未再検証

計測ツール自身が七回測り間違え、そのどれもが自分に不利な方向だった

対話型 TUI の A/B harness には七つの計測誤りがあり、さらにコードのコメントにだけ書かれた二つがありました。どれも本物の pi の上で見つかり、どれも数字を実際より良く見せる方向でした——捏造された結果が 8 件、一度も送られなかったプロンプトが 5 セル。

TUIA/B計測の欠陥infra_void自己申告の欠陥

この記事は、計測ツール自身の誤りの記録です。

commit b0eb0680 の一文目は、はっきりこう書いています。

これは体裁の手直しではない。どれも本物の pi 0.87.1 の TUI の上で見つけたもので、どれも harness を自分に不利に、または agent に有利にする方向だった。つまりこれらの数字は本来、実際より良く見えるはずだった。(原文:「這不是排版修飾。每一條都在真 pi 0.87.1 的 TUI 上抓到,而且每一條的方向都是讓 harness 對自己不利或對 agent 有利,也就是說這些數字原本會長得比實際好看。」)

「方向がそろっている」ことが鍵です。七つの誤りの方向がランダムなら、それはノイズです。すべて同じ方向なら、それは系統的なものです——この harness に構造的な問題があることを示しています。


#なぜ対話型 TUI は特に測りにくいのか

対話型インターフェースには、叩く API がありません。測りたいもの(ゲートのバナーがどこに出るか、モデルが何ターン目まで進んだか、settle したかどうか)は、すべて画面からかき出す必要があります。

測り方は tmux の疑似端末です。pane を一つ開いて agent を走らせ、止まるのを待ち、capture-pane で画面を取り込み、正規表現でその中から判定を探します。

これが七つの誤りの出どころです——どれも「画面からどう真実を読み取るか」という層にあります。


#誤り 1:arm A が別のモデルで走っていた

「Vacant を入れていない agent の成績」を測るには、まったく入れていない arm が一つ必要です。問題は、モデルの接続先が起動引数ではなく設定から来ることです。

やり方は、models.json をテスト環境に一部コピーすることでした。しかしそれで pi がその provider を選ぶわけではありません。 起動時の固定指定がないと、pi は自分のデフォルトを使います。

実測では openrouter/moonshotai/kimi-k2.6 に落ちていました——違うモデル、違うホストで、しかもこの実行の proxy を 1 byte も通っていませんでした。

その結果、arm A は「モデルの失敗」を「agent の失敗」として記録しました。 A アームのすべてのセルが、間違ったものを測っていたのです。

コードのコメントにはこうあります。

⚠ THIS IS THE WHOLE A/B, AND I GOT IT WRONG THE FIRST TIME.

修正:各セルで seen_model を記録し、報告側で異なるモデルのセルの比較を拒否できるようにしました(INVALID 欄)。

興味深いのは、repo 自身が 2026-09-22 に同じ穴にはまっていたことです。gateshim.py:578-582 に書いてあります。新しい問題ではなく、誰も両者を突き合わせなかったのです。


#誤り 2:settle のシグナルを取り違えた

元のルール:B/C アームはゲートのバナーが見えた時点で settled と報告する。

問題は、arm C だけが複数ラウンドを走るアームだったことです。第一ラウンドのバナーが出た途端に報告し、すぐに session を殺していました。スナップショットが捉えたのは、まだ考えている第二ラウンドでした——token は ↑5.9k ↓79 で止まり、第一ラウンドの判定はすでに可視領域から押し出されていました。

そのセルは、settled: true と gate_accepted: null の組として読み取られました。

コードのコメント:

両方の半分とも、嘘をついているのは harness だ。 そのアームが嘘をついているのではない。

修正:完全な scrollback を取り込み、ポーリングで回数を数え、TUI がアイドルに戻ってから初めて settle とする。


#誤り 3:可視の高さしか読んでいなかった

capture-pane はデフォルトでは、見えている約 50 行しか返しません。-S -4000 に変えて、完全な scrollback を取ります。

なぜこれで数字が良く見えるのか:複数ラウンドの session では早い時点の判定が画面の外へスクロールしてしまい、それこそが測りたい変数だからです。 可視の高さだけを読むことは、系統的に最後の判定だけを見ることに等しく——そして最後の判定はたいてい有利なほうです。


#誤り 4:前置きを判定と取り違えた

ゲートはまずこの行を出力します。

Vacant 閘門:驗收來源 dir:tests_visible(…);快照 sha256 …

本当の判定はこうです。

✓ 可見驗收全過(2/2)
✗ 拒交裁決(visible_fail,可見驗收 0/2)——互動模式不擋檔案:…

どちらも「Vacant 閘門:」で始まり、先頭はまったく同じですが、前者には判定がありません。

元の正規表現は先頭しか見ていなかったので、saw_gate が真になり、arm C は前置きの後で settled を宣言しました。どのセルも、返ってくるのは gate_accepted: null でした。

これはバッチ全体で最も深刻な誤りでした。全か無かだからです。あるセルを読み違えたのではなく、すべてのセルが読み取れなかったのです。

修正:二か所の正規表現の両方で、マーク(✓ / ✗ / ⚠)を必須にしました。

ついでに、括弧の形の落とし穴もあります。納品拒否のバナーに書かれているのは 可見驗收 0/2) です——コンマ+空白で、左括弧がありません(左括弧は stop reason のほうで使われている)。合格のバナーだけが 可見驗收全過(2/2) です。合格のほうの形だけに合わせて正規表現を書くと、すべての納品拒否が None として parse されます。


#誤り 5:infra_void が納品拒否として記録された

bool(None) は Python では False です。したがって:

gate_accepted = None   (ゲートに判定がない)
↓
False              (「判定:拒否」として記録)

これは repo 自身の infra_void ルールが明文で禁じていることです:void を、数えられる失敗に変えること。 何も起きなかったセルと、納品拒否されたセルとでは、意味がまったく違います。

コードのコメント:

None is a real value here, not a missing one.


#誤り 6:同じ記録の中で二つのフィールドが矛盾していた

まず「最後の合格」で visible を設定し、その後「最後の拒否」で上書きしていました。

その結果:最終ラウンドが 2/2 全合格のセルが、1/2 と報告されました。

修正:最後のバナー一つで一度に決める。

これは比較的小さな誤りですが、方向は同じです——より悲観的な結果を報告していました。


#誤り 7:停滞が agent の失敗として数えられた

実測のシグネチャによれば、モデルは存在しないツール ls_r を呼び、shell に退き、それ以降何も出力しなくなりました。一方で backend 自体は健全でした(直後に 2 秒で say ok を返した)。

これは失敗ではなく void です。モデル呼び出しの失敗は、正しい答えでも誤った答えでもありません。

コードのコメント:

A failed model call is neither a right answer nor a wrong one; it is a void, and a void is re-run, not counted.

修正:クリーンな workspace で再実行し、決して採点せず、attempts/voids を記録する。MAX_ATTEMPTS = 3。


#コードのコメントにだけある二つ

この二つは commit message には挙がっておらず、私が ab_harness/tui_ab.py のコメントを読んでいて見つけたものです。

#arm A の 8 セルはすべて捏造された結果だった

pi のステータスバーは、送信前は 0.0%/262k で、始まってから ↑N ↓M が出ます。

24 セルのバッチで、arm A の 8 セルはすべて、ラウンドの間ずっと 0.0% のままでした。プロンプトは一度も送られず、TUI はただ座っていただけです。そして古い arm-A の settle ルール(「idle 4 polls」——即座に成立)は、それらをすべて settled + hidden_pass: false として記録しました。

コードのコメント:

それは 8 個の捏造された結果だ。 The only thing that would have caught it is this check.

arm A のすべてのセルが間違っていました——数字の偏りではなく、セルそのものが存在しなかったのです。

#Enter が 6 セルで落ち、しかも私は二度捕まえ方を間違えた

pi は起動時にこう出力します。

Warning: tmux extended-keys is off. Modified Enter keys may not work.

最初の 24 セルのバッチでは 6 セルで Enter が落ちていました。形はどれも同じです。プロンプトが入力欄に残り、ステータスバーは 0.0%/262k、ターンもゲートも判定もない。それらのセルは timeout として記録されました。

これが、arm B で 8 セルのうち 5 セルが void になったように見えた理由です。実際には、8 セルのうち 5 セルでプロンプトが一度も送られていなかったのです。

作者は、自分が捕まえ方を二度間違えたことを書き残しています。

  1. grep -F "$prompt" で確認した——pi はプロンプトを会話の記録にエコーするので、文字列は常に画面上にあり、この検査は決して通りません。そこで彼は Enter を十回送り直し、すべてのセルで exit 2 していました——画面に ↑2.8k ↓34 と正しい provider がはっきり表示されているセルも含めて。これでは arm A が 8/8 submit_failed と報告されてしまいますが、arm A は実際には毎回送信していました。
  2. tmux set -g extended-keys on——測ってみるとオフで 3/3 送信、オンで 3/3 送信で、最初から問題ではなかったうえに、Enter のエンコーディングを変えてしまいます。

コードのコメント:

both recorded because the second one is worse than having no check at all

二つ目の間違い方は、検査がないよりも悪いものです。正しい答えを誤りとして報告してしまうからです。


#修正した後

read_verdict には、五種類の実際の pane の形に対するテストがあります。

  1. 複数ラウンドで最後に合格
  2. 単一ラウンドで拒否
  3. 複数ラウンドで最後に拒否
  4. infra_void
  5. ゲートなし(arm A)

ruff はクリーンです。

しかしそのバッチの数字は使えません——A アームの 8 セルは捏造で、B アームの 5 セルはプロンプトが一度も送られていません。直した後で再実行が必要です。

リモートの vacant-dev は、arm-A のセル 3 つの後でマシンごと到達不能になりました(port 22、ICMP、tailscale ping のすべてに応答なし、同じネットワークセグメントの他のマシンは正常)。「それらのセルにはバックアップがなく、復旧したらまず引き戻す必要がある。」


#この七つから私が学んだこと

この節は私の判断です。

1. 同じ方向の誤りこそが要点です。

七つの方向がランダムなら、それはノイズです。しかし「どれもが数字を実際より良く見せる」なら、それは構造的な問題を示しています——具体的には、この harness が系統的に「何も起きていないように見える」より「何かが起きたように見える」ほうを好むということです。

これはクローラーによくある病です。何も取れなかったとき、人(とプログラム)は「起きなかった」ではなく「失敗した」と記録しがちです。後者のほうが、データがあるように感じられるからです。

2. 「静かな無効」は「効果が小さい」よりも、誤りとして報告されるべきです。

もし W2(opencode に stop イベントがない)が「トレースは opencode では効果が小さい」と読まれたら、それは受け入れられる結論であり、誰も直そうとはしないでしょう。

しかし実際には、37 step の完全な署名付きカルテがあり、判定はゼロ、delivery.md もありません。見た目はまったく正常です。

37 step の完全な署名付きカルテ、判定はゼロ。これは何もしないより危険です。receipt が正しく見えてしまうからです。

3. 計測ツールのバグは、製品のバグと同じ基準で管理すべきです。

この七つは commit message では「eval(tui)」で、「feat(eval)」と同じ種類に見えます。しかし方法の上では、製品の欠陥と何の違いもありません——どちらも「成立していると思わせて、実際には成立していない」ものです。

名指しの異議:なぜ分岐を記録に書き込むのか|R455 の構造的な上限|ネイティブプラットフォームと静かな無効

関連

約 12 分で読めます

世新大学世新大学 情報伝播学科Vacant

世新大学 情報伝播学科 2026 年度卒業研究・展示会