ネイティブプラットフォームと静かな無効
判定点を agent のプロセスの中に置くと、プラットフォームが協力しないとき「効果が小さい」のではなく、静かに無効になります——カルテは完全で、署名チェーンは検証を通り、判定はゼロです。三つの欠落と、ループを親プロセスに移すという一つの決定について記します。
2026-09-29 時点でのこの線の状態は設計上の決定であり、効果は未測定です。このページは、そこで測られた三つの欠落を記録します。
#三つの欠落
#W1 — 経路の仲介が認識する鍵の変数は二種類だけ
envmap.py の SECRET_VARS からは OPENROUTER_API_KEY が削除されており、discover_keys() は openai と anthropic の二つしか読みません。
測られた結果:opencode の openrouter provider はローカルで 1.2 秒以内に rc=1、requests_seen = 0 となり、wire は一度も送られませんでした。固定ポートの openai-compatible provider に替えると requests_seen は 2 になりました——経路の仲介は opencode に対して通じていますが、opencode は依然として非ゼロのコードで終了します。この道は最後まで通っていません。
#W2 — opencode には stop に相当するイベントがない
vacant install が生成する opencode plugin が登録するのは chat.message、tool.execute.before、tool.execute.after だけです。pi の同名のテンプレートには ask("stop", …) がありますが、opencode のテンプレートにはありません。Python 側の EVENT_MAP には "opencode": {… "stop": "stop" …} があります。したがって欠けているのは plugin の側です。
一つのセルを実測したときの署名チェーンのイベント集計:
trace_genesis 1, prompt 3, session_seen 2, step 37,
session_closed 3, unrecorded_change 1
stop / finding / feedback 事件: 0
delivery.md / delivery.json: 不存在
作者自身の言葉:
37 個の step の完全な署名付きカルテ、判定はゼロ。これはやらないより危険だ。receipt が正しく見えてしまうからだ。(原文:「37 個 step 的完整簽章病歷、零個裁決。這比不做更危險,因為 receipt 會看起來是對的。」)
#W3 — opencode のプロジェクトルートは cwd では決まらない
agent の言葉そのままでは「There is no TASK.md in the working directory (/home/user1/ab-20260928)」です。ワークスペースにファイルが一つしかないときは 100% 失敗し、ファイルが多いときはたまたま成功します。arm C では三つのセルがクラッシュしました(3 個の step_start の後に rc=1、stderr なし、429 なし)。
この件は作者自身が自分の誤りだと認めており、修正済みです(--dir + git init workspace)。
#なぜこの三つは同じ一つの誤りなのか
W1 と W2 はどちらも同じことをしています。判定点を agent のプロセスの中に置いているのです。W1 はネットワーク層で、W2 はターンの境界で。
そしてプラットフォームが協力しないとき、経路全体は「効果が小さい」のではなく、静かに無効になる。
#決定:ループを親プロセスに移す
「spawn できる CLI である」agent プラットフォームはどれも自動的に T3 の能力を得る。フックも、wire も、鍵の変数のホワイトリストも要らない。フックは必要条件から加速器(ターン内での途中フィードバック)に格下げされ、フックがなくてもレベルは下がらない。
T0–T3 の階層の現状:
| 層 | 状態 |
|---|---|
| T3(bridge、親プロセス) | 使用可能——20 セル中 17 accept、3 reject、17 セルの release はすべて readback_ok=true。ゲートは wire も Stop hook も必要としない |
| T2(この決定) | 使用可能 |
| T1(tool-call のフックだけ) | 死んでいると測定された(opencode) |
| T0 | 時代遅れ |
新しい形は T1 を T3 に引き上げ、しかも T1 は自己申告しなければなりません。フックが最後まで動いたのに判定がなかったときは、「今回は判定がなかった」と明言する記録を出力します。
#Wave 1:同符号・未解決
20 問 × 三アーム(R534 問題バンク):
| アーム | 中身 | 非公開検査がすべて正解 | 納品 | 納品拒否 |
|---|---|---|---|---|
| A plain | opencode、何もなし | 19/20 (95.0%) | 20/20 | 0 |
| B zero-cfg | vacant install の既定 + opencode のネイティブ plugin | 17/20 (85.0%) | 19/20 | 1 |
| C bridge | native_acceptance_bridge:prepare/judge/release | 17/20 (85.0%) | 17/20 | 3 |
#対話型 TUI の七つの測定誤り
この節はこのページで最も記録しておくべき部分です。どの誤りも、数字を実際より見栄えよくする方向だったからです。
| # | 誤り | 実際の結果 |
|---|---|---|
| 1 | arm A が別のモデルで動いた | models.json をコピーするだけでは pi はその provider を選びません。実測では openrouter/moonshotai/kimi-k2.6 に落ち、しかもこの実行の proxy を通った byte は一つもありませんでした——arm A は「モデルの失敗」を「agent の失敗」として記録しました |
| 2 | settle が信号を取り違えた | arm C は複数ラウンドを走る唯一のアームで、第一ラウンドのバナーが出た瞬間に settled と報告して session を終了させ、スナップショットは第二ラウンドがまだ考えている最中を捉えていました(token は ↑5.9k ↓79 で凍結)。どちらの半分も harness が嘘をついていました |
| 3 | 見えている高さだけを読んだ | 約 50 行 vs 完全な scrollback の 4000 行で、先に出た判定はちょうど画面外にスクロールしていました——測るべき変数が切り落とされていました |
| 4 | 前置きを判定と取り違えた | ゲートが先に出力する「驗收來源 dir:tests_visible…」は判定と同じ書き出しですが判定ではなく、そのためどのセルも gate_accepted: null で返ってきました |
| 5 | infra_void が納品拒否として記録された | bool(None) が「ゲートに判定がない」を「拒否と判定された」として分類しました——void を数えられる失敗に変えることは、まさに repo 自身の infra_void 規則が禁じていることです |
| 6 | 二つのフィールドが互いに矛盾した | 最終ラウンドが 2/2 のセルが 1/2 と報告されました |
| 7 | 停滞が agent の失敗として数えられた | モデルが存在しないツール ls_r を呼び、shell に戻ったきり何も出力しなくなりましたが、backend 自体は健全でした(直後に 2 秒で say ok を返しました) |
ほかにコードのコメントにはあって、commit message には挙げられていないものが二つあります。
- arm A の 8 セルはすべて、ラウンド全体を通して
0.0%で止まっていました。プロンプトが一度も送られず、TUI はただ座っていただけなのに、古い settle の規則(「idle 4 polls」、すぐに成り立つ)が全部をsettled+hidden_pass: falseと記録しました。それは 8 個のでっち上げの結果です。 - Enter が 6 セルで落ちました:形はどれも同じで、プロンプトが入力欄に残り、ステータス行は
0.0%/262kです。これが arm B で 8 セル中 5 セルが void になったように見えた原因で、真相は 5 セルでプロンプトが一度も送られていなかったことです。
すべて修正済みで、五種類の実際の pane の形(複数ラウンドで最後に合格/単一ラウンドで拒否/複数ラウンドで最後に拒否/infra_void/ゲートなし)にテストを加えました。
測定ツール自身が七回測り間違え、そのたびに自分に不利な方向だった
#pi の対話モード:インストールしても、していないのと同じ
一つ目の欠陥はとくに記録しておく価値があります。インストールは成功と報告され、proxy は起動したのに、すべての呼び出しが sink し、原因を示すエラーメッセージは一つもありませんでした。 install() の呼び出しが upstreams/models/python を渡しておらず、KEY_FROM="" と UPSTREAM="" がレンダリングされていました。
#汎用の受け取り口の五つの静かな欠陥
同じ病気です。エンドツーエンドでは測れて、単体テストでは捕まえられません。
最も深刻なもの:Codex の常設インストールはもともと profile = "vacant" を使っていましたが、0.156.1 はこれを旧来のキーとみなして起動を拒否しました。作者は「agent が起動できなくなるインストールは、インストールしないより悪い——このセルは単体テストだけでは捕まえられない」と書いています(原文:「一個會讓 agent 起不來的安裝比沒裝更糟——這一格如果只做單元測試是抓不到的。」)。
もう一つ:OPENCODE_CONFIG_DIR を実行ごとの追加層として使うと、グローバルな AGENTS.md の読み込み位置も同時に変わってしまい、利用者のグローバルな規則が静かにプロンプトから消えます。
#誠実な限界
- 対話モードのゲートは納品を止めません——ファイルはすでにワークスペースの中にあり、ゲートは印を付けるのであって、差し止めるのではありません。agent は extension を削除できます。
/vacant offは利用者の選択であって失効ではありませんが、extension はフックのログにvacant_offを一件残し、レシートはそれについて嘘をつきません。 - bridge の CONFORM モードは再試行を「外側の harness が新しい完全な session を開く」ことに任せていますが、フィードバックを次の session のプロンプトにどう送るかを bridge は定義していません。
- 同じ OS アカウント:arm どうしはパスの分離 +
chmod 0444で隔てられているだけで、アカウントをまたいだ強制ではありません。 - R535 の予想失敗率は設計上の意図であって、測定値ではありません。
- 効果は未測定です。 無料モデルの 1 日 1000 リクエストの上限がバッチの途中で尽き(arm B の 2 セルが 429、wave 2 pilot はすべて 429)、arm 間のどんな差も Vacant に帰属させることはできません。
#次に
- 製品の形:ゼロ設定の Vacant
- 手順までたどる:アカウンタブル・トレース
- 位置づけの出どころ:実行可能な受け入れ層
- 全体の輪郭:システム構成

