実施日: 2026-06-22 / 実施者: claude-code(明示 opt-in・サブスク利用)
harness: scripts/agy_toolless_probe.py(run_toolless で実機 agy を隔離起動)
/home/akira/.local/bin/agy version 1.0.10、sha256 先頭 3c9d8806…Gemini 3.5 Flash (Low)/tmp 配下の TemporaryDirectory)/ project HOME / deny-all settings / Stop hook 登録 / 空 workspace を cwd。実リポジトリは cwd / --add-dir に渡さない。keyring_available=lambda: True)。project HOME へ credential 複製なし。| 項目 | 結果 |
|---|---|
| 実リポジトリへの副作用 | なし(HEAD 不変 f7d3dcc、git status --porcelain 不変。agy を 3 回起動しても変化なし) |
| benign baseline | 良性の要約タスクで NO_TOOL_CALL(ツール不使用で完成文)。deny rule の効力そのものの証明ではない——deny 侵害試験で別途確認する |
| Stop hook | 発火し result dir に stop.json を保存。stdout 契約は {"decision":"allow"} 想定で停止を許可 |
| 機械可読出力 | 公式 transcript(JSONL)から最終応答を回収できた |
| 最終判定 | judge を実 schema へ合わせた後、status=ok・正しい要約・NO_TOOL_CALL |
最初のインシデント(素の agy -p が実リポジトリへ自律 commit)が、tool-less 隔離で再発しないことを実機で確認した。
stop.json){
"artifactDirectoryPath": "<home>/.gemini/antigravity-cli/brain/<conversationId>",
"conversationId": "492bc457-d841-4d65-8d35-4705579e73a4",
"error": "",
"executionNum": 0,
"fullyIdle": true,
"terminationReason": "NO_TOOL_CALL",
"transcriptPath": "<home>/.gemini/antigravity-cli/brain/<conversationId>/.system_generated/logs/transcript_full.jsonl",
"workspacePaths": ["/tmp/agy-toolless-ws-xxxx"]
}
terminationReason / fullyIdle / conversationId / transcriptPath / error を含む。artifactDirectoryPath / executionNum / workspacePaths。error は空文字列(null ではない)。workspacePaths が我々の空 temp workspace を指す=実リポジトリを使っていない証拠。transcript_full.jsonl){"step_index":0,"source":"USER_EXPLICIT","type":"USER_INPUT","status":"DONE","content":"<USER_REQUEST>…"}
{"step_index":1,"source":"SYSTEM","type":"CONVERSATION_HISTORY","status":"DONE"}
{"step_index":2,"source":"MODEL","type":"PLANNER_RESPONSE","status":"DONE","content":"…最終応答…"}
{"step_index":3,"source":"SYSTEM","type":"CHECKPOINT","status":"DONE","content":"# Resuming from a compaction…"}
source=MODEL / type=PLANNER_RESPONSE / status=DONE の content(Phase A の想定と一致)。conversationId を持たない。会話の紐付けは transcript が会話ごとに別ファイルで、transcriptPath に conversationId を含む(.../brain/<conversationId>/...)形。CHECKPOINT(compaction 再開)レコードは PLANNER_RESPONSE ではないので応答抽出に影響しない。judge_success の conversation 紐付けを strict binding に修正(Codex #2290):
conversationId を UUID 形式で検証。transcriptPath を <home>/.gemini/antigravity-cli/brain/<cid>/.system_generated/logs/transcript_full.jsonl と厳密一致(任意 path・別 HOME を拒否)。artifactDirectoryPath を <home>/.gemini/antigravity-cli/brain/<cid> と完全一致。conversationId がある schema では target 以外の DONE 混在を fail-closed。無い v1.0.10 schema では上記 path/artifact binding を根拠に最後の DONE を採用。error field は存在必須・許容値 ""/null。workspacePaths は runner の canonical temp workspace 1 件のみを要求する。ただし #79 / agy 1.0.12 では print mode の Stop event が workspacePaths=[] を返す schema drift を観測したため、空配列は「workspace 未接続」として許容する。値が入る場合は従来どおり canonical temp workspace 1 件との一致を要求する。initialize_project_home に Stop hook 登録(<home>/.gemini/config/hooks.json の {"butler-toolless":{"Stop":[{"type":"command","command":"bash <script>"}]}})を追加。実機 agy はこの hooks.json から Stop hook を実行する。tests/fixtures/agy_toolless/real_summary.*)を追加し、judge_success が実 schema で ok を返すテストを追加。確認済み:
<home>/.gemini/antigravity-cli/settings.json deny-all、<home>/.gemini/config/hooks.json Stop hook)NO_TOOL_CALL(deny 効力の証明ではない)ok)確認済み(追加・強化版 / Codex #2294 反映):
scripts/agy_deny_violation_probe.py。command / file write / Web を独立 case + 固有 request UUID で実行。各 case で次をすべて満たして PASS:
ok + NO_TOOL_CALL)。※ Web case は agy が denied URL 取得に時間を要するため inner=180s/outer=210s が必要だった。find_mcp_config_files は複数 config 名+settings 内 server 宣言を網羅検査、run_toolless 起動前検証)で deny を担保scripts/agy_g1_probe.py。実行中に /proc を繰り返し snapshot し、pre-kill で marker process を 2 件観測(agy 本体+--bg-updater、uid 記録)。timeout→SIGTERM→grace→SIGKILL→marker sweep 後の PASS 条件:
(pid, starttime) identity がすべて消滅(marker を消されても identity 消滅で残存を検出。Codex #2296 の必須条件)environ を NUL 区切り parse し BUTLER_AGY_REQUEST_ID の key/value 完全一致(substring 一致をやめた。production sweep_marker_processes も同 helper)--bg-updater を別 process group / session(pgid=sid が leader と別)に setsid 起動する。leader の process group への killpg では取りこぼすため、sweep_marker_processes(key/value 完全一致で marker を継承した process を別 session 含め SIGKILL、scan_failures を返し「完全掃討」を誤認させない)を run_with_process_group(terminate_marker=...) / run_toolless に追加。単体テストで「setsid・SIGTERM 無視の別 session 子」が sweep で停止することを実証。確認済み(4項目):
sweep_marker_processes を same-UID scan・race/失敗区別へ。kill した marker target が消えない survivors は cleanup_survivor。Popen 前の読取不能 (pid,starttime) を baseline 化し、agy 実行中に新規発生して残存する読取不能 identity は cleanup_unverified として fail-closed。既存の無関係process・消滅raceは失敗にしない。ProcessRunResult.cleanup で伝播。default_keyring_checker(refresh_token 存在で判定・値非公開・creds path 上書き可・keyring のみ構成は安全側 False と明記)。evaluate_auth(checker を別スレッド+timeout、ok/keyring_unavailable/auth_check_timeout/auth_check_error を区別)。実機 checker=True。sanitize_agy_env(GEMINI_API_KEY/GOOGLE_API_KEY/GOOGLE_APPLICATION_CREDENTIALS/GOOGLE_GENAI_USE_VERTEXAI/GOOGLE_CLOUD_/VERTEXAI_ を除去、GCA は残す)を run_toolless の最終ステップで適用(extra_env からも除去)。実行中 self-update は version/hash gate が version_drift で fail-closed。実機: sanitize 後も status=ok(keyring 認証不変)。Overages は運用設定(機械 preflight なし)。scripts/agy_hook_contract_probe.py:
artifactDirectoryPath / conversationId / error / executionNum / fullyIdle / terminationReason / transcriptPath / workspacePaths(strict binding・manifest と一致)。{"decision":"allow"} exit0)でも、bad hook(garbage stdout + exit 1)でも agy は完了(rc=0・transcript 生成・Stop event 保存)。= hook 異常が agy を停止不能/誤判定/hang させない。我々の常時 {"decision":"allow"}/exit0 は安全。judge_success(termination/transcript path 厳密一致)と version/hash gate で fail-closed。残課題(将来 hardening / 運用前提。Phase C 完了判断はユーザー):