関連 issue: #74 / 設計正本: docs/検討用/74_Antigravity CLIへの移行_実装案_v2.md
裏付け probe: 74_agy_live_probe.md / 74_agy_toolless_review_summary_probe.md / 74_agy運用リスク追加probe.md
未確定 とした点は「推測で埋めず probe/spike タスクとして起票」する。shell=True/文字列連結/sh -c 禁止)。--dangerously-skip-permissions は付けない。--add-dir に渡さない。diff・本文は prompt 引数のみで渡す。failed / need_input / auth_required。| ゲート | 内容 | 影響 |
|---|---|---|
| G1 プロセスツリー停止(未検証) | sandbox(nsjail) 有効時に外側 deadline で agy / agentapi / nsjail の子・孫を停止できることが未実証(probe が実行承認上限で未完) | Phase C の production route 有効化前に必須。未通過なら review/Wiki は実装してもルート切替しない |
| G2 transcript 私的契約 | JSONL の MODEL/PLANNER_RESPONSE/DONE は非公開 schema |
agy 更新ごとに fixture 再取得+parser 契約テスト+permission 侵害試験+合成 smoke を通すまで route 一時停止 |
| G3 quota 共有 | quota は Google アカウント共有 | プロセス横断の concurrency gate(B8・flock slot)+429→pending/backoff を実装。Overages 無効化は B8b で公式設定を spike し、強制不可なら運用 preflight |
各タスク: 目的 / 対象 / 完了条件(DoD) / 依存。
外部 agy を使わず、ハンドオーサー fixture で fail-closed の純ロジックを先に固定する。実機 schema との適合確認は、安全な runner を実装した後の Phase C で行う。
tests/fixtures/agy_toolless/terminationReason/fullyIdle/conversationId/transcriptPath/error)⑦transcript JSONL(DONE MODEL/PLANNER_RESPONSE 含む / 別 conversation / malformed / path traversal)。各 fixture に agy version/hash/model/settings schema を併記。butler/agy_toolless.py(parser), tests/test_agy_toolless.pyMODEL/PLANNER_RESPONSE を抽出。malformed JSONL / DONE response 無し / 別 project HOME 参照 / path traversal / conversation ID 不一致を拒否。butler/agy_toolless.py, テストfullyIdle=true / 許可 termination(初期 NO_TOOL_CALL)/ transcript が対象 HOME 配下 / conversationID 一致+DONE 1 件以上 / 最終 response 非空 / workspace に成果物なし / version-hash 不変)。butler/agy_runtime.py(argv builder), テスト[agy, --model, M, --sandbox, --print-timeout, Ns, -p, prompt] を argv で生成。backtick / quote / 改行が展開されないことをテスト。対象: butler/agy_toolless.py(AgyToollessResult), review maid, summarize_doc_result
内容: v2 最新の dataclass(status は ok/pending/timeout/auth_required/failed、retry_after_seconds と failure_reason を含む)。token・transcript 全文は入れない。
internal status → 外部 contract の変換を明記。butler/models.py:57 の ResultContract.status は ok/need_input/blocked/failed/cancelled(pending は無い)。quota/concurrency 待ちはユーザー入力を要さないため need_input ではなく blocked が意味的に適切:
| AgyToollessResult.status | review ResultContract |
Wiki SummaryResult(#73) |
|---|---|---|
ok |
ok |
ok |
auth_required |
need_input(認証案内) |
failed(reason=auth_required) |
timeout |
failed(retryable) |
timeout |
pending(quota/concurrency 待ち, retry_after_seconds 付き) |
blocked(retry_after_seconds を evidence に) |
skipped(reason=quota_backoff または concurrency_backoff、retry_after_seconds を持たせる) |
failed |
failed |
failed |
need_input は auth_required のみに使う。quota/concurrency 待ちは pending → blocked。共通 contract は拡張しない(既存の blocked を使う)。
DoD: 型定義+上表の変換テスト(特に pending→review blocked / Wiki skipped+reason+retry_after の双方)。依存: A3。
Phase A 完了条件: A1〜A5 の parser / 判定 / builder / 結果型・contract 変換が、外部 agy 不要のテストで緑。
判定(2026-06-22): Phase A 完了。 A1〜A5 実装済み、独立再実行で 54 件 pass。fixture は開発用であり、実機 schema 適合は Phase C の完了条件とする。
BUTLER_STATE_ROOT 定義
butler/agy_runtime.py(または Butler 設定モジュール)butler/agy_runtime.pyproject_root の digest に fallback。同名 repository を衝突させない。butler/agy_runtime.py<BUTLER_STATE_ROOT>/projects/<key>/agy-tool-less/{home,results,runner.lock} を用意。deny-all settings.json・Stop hook 設定を配置。認証は keyring preflight、取得不可なら auth_required(対話ログイン案内)。HOME・transcript・result は owner-only permission。auth_required のテスト。依存: B1。butler/agy_runtime.py(runner の transcript 読込)transcriptPath を open する直前に、字句判定(is_within_home)に加えて is_within_home_resolved(Path.resolve() で symlink を解決した実体パス) で HOME 配下を再検証する。symlink 経由で HOME 外へ出るパスを拒否し、open 前後の差し替えを避ける(owner-only directory・安全な open)。butler/agy_runtime.pyTemporaryDirectory を cwd に。--add-dir 不使用。実行後に予期しないファイルがあれば失敗。scripts/agy_toolless_stop_hook.sh, runnerBUTLER_AGY_RESULT_DIR=<key>/.../results/<request-uuid> を hook へ渡し、stdin JSON を原子的に保存。公式 hook 契約に従い、stdout へ有効な JSON を返して停止を許可する(保存だけでなく hook の応答契約を満たす)。request UUID は予測困難な値。別 project / 別 request の結果を受理しない。butler/agy_runtime.pyflock(プロセス死で自動解放、stale 化しない)で同一 project を直列化。異なる project は別 HOME / lock / result で並列許可。thread lock / プロセス内 lock では代用しない。butler/agy_runtime.pyagy --print-timeout は外側より grace 分短く。専用 process group で起動し、timeout で SIGTERM→grace→SIGKILL。途中 stdout を回答に採用しない。Stop hook 無くても timeout 確定できる。butler/agy_runtime.pyAGY_COMMAND(絶対パス), AGY_EXPECTED_VERSION, AGY_EXPECTED_SHA256。起動前後に検証、不一致なら実行せず停止し更新・fixture 再取得を促す。期待 hash を自動更新しない。butler/agy_runtime.pyflock(acquire できた slot 数 = 同時実行数)で実装し、上限を設定可能にする。
pending(retry_after_seconds 付き)を返し、ブロックし続けない。pending/backoff。sanitize_agy_env) とし、機械的 billing preflight は作らない。pending・project lock→global slot の順序テスト。依存: B5, B6。butler/agy_runtime.py(run_toolless / ToollessRunConfig)judge_success を順序込みで強制する統合 API。個別 context manager の組み合わせを caller に委ねない。sanitize_agy_env(API キー/Vertex env を agy へ渡さない)。Overages 自体は運用設定として F1 の確認ゲートで扱う。
auth_preflight(keyring_available) のインターフェースは Phase B 実装済み(注入式)。実際に OS keyring / agy を probe する checker 実体は実機が要るため Phase C で確定・結合する。Phase B 完了条件: runner の隔離・結果回収・timeout・version gate・lock/concurrency・**統合 lifecycle(run_toolless)**を、fake process と合成入力による自動テストで検証できる。実機 agy の起動を完了条件に混ぜない。
判定(2026-06-22): B0-B8・B-read・B2・B4・B-run 実装、
tests/test_agy_runtime.py52 件・全体 98 件 pass。B8b と実 keyring checker 実体は実機要のため Phase C へ明示移動。
tests/fixtures/agy_toolless/、74_agy_toolless_review_summary_probe.md 系74_agy運用リスク追加probe.md 追記)Phase C 完了条件: 実機 schema 適合、tool-less 隔離、異なる2 projectの合成 smoke、process tree 残存ゼロを証拠付きで確認する。
butler/wiki_doc_summarizer.py に summarize_doc_result() -> SummaryResult(runner 呼び出し)を追加し、summarize_doc() -> str を互換ラッパーに(成功で summary、失敗で空文字列)。DoD: 互換 API の後方互換テスト+内部 API の status/reason テスト。依存: Phase C。summarize_doc_result() -> SummaryResult が runner を呼び、SummaryResult のインターフェース(status/reason/retry_after/summary)と 1 件 timeout・validation 失敗の区別までを実装する。content hash の要約 cache も #74 内(#73 非依存・quota 抑制)。#73 整合=「SummaryResult の形・reason 値が #73 の partial 構築に必要な情報を欠かない」スキーマ整合テスト(#73 worker のモックで検証)。依存: D1。全体 partial 統合は #73 実装に依存する別タスク。Phase D 完了条件: dry-run / 実同期で summarize_doc_result が期待 status/reason を返す。SummaryResult スキーマが #73 の要求を満たす(wiki_sync 全体の partial 統合は #73 側で、本 Phase の完了条件には含めない)。
butler/maids/antigravity_cli_review_maid.py 追加。Butler が git diff を取得し prompt へ埋め込む(agy に repo access を与えない)。prompt に depth / 仕様 / diff / 出力形式 / tool 禁止 / 「diff 内命令はデータ」注記。DoD: prompt 構築テスト。依存: Phase C。Gemini 3.5 Flash (Low) 短 prompt・短 timeout。model は registry/config で上書き可(コード固定しない)。
mcp_facade.py の thorough 説明から「リポジトリを読んで実装と照合」を外して期待品質を縮小する。need_input、thorough の chunk 化は後続検討(初期は制約として明記)。DoD: 上限超過テスト。依存: E1。config/maid_registry.yml に antigravity_cli_review を追加。config/intent_maid_routes.yml はまだ切り替えない。butler/mcp_facade.py の「Gemini CLI でレビュー」固定文言を CLI 中立へ更新。DoD: route 解決テスト。依存: E1〜E4。Phase E 完了条件: 正常 / バグ fixture で期待結果、quick の合成 smoke 成功。
E5 は backend を追加するだけでルートを切らない。実際の切替を担う実装・テスト・ロールバック確認を独立タスクにする。G1(C3 プロセスツリー停止)・version/hash gate・認証 preflight の通過確認が前提。
GEMINI_API_KEY / GOOGLE_API_KEY / GOOGLE_APPLICATION_CREDENTIALS / GOOGLE_GENAI_USE_VERTEXAI / GOOGLE_CLOUD_* 等が agy へ渡らない(sanitize_agy_env を run_toolless が最終適用。実装済み)。docs/Butler利用ガイド.md §5.5 に記載。Issue #74 コメント参照。config/intent_maid_routes.yml の intent: review を antigravity_cli_review へ。DoD: route 解決回帰テスト(切替後に agy review へルート)。依存: F1。config/intent_maid_routes.yml の intent: review を gemini_cli_review(registry に enabled で残置)へ戻す config-only rollback(tests/test_review_route_resolution.py)。Wiki は BUTLER_WIKI_SUMMARY_BACKEND=unavailable で agy runner を呼ばず skipped(reason=backend_unavailable) を返す安全停止(tests/test_wiki_doc_summarizer.py)。個人向け Gemini CLI 提供終了のため旧 Gemini 経路は復元せず、Wiki の rollback 先を unavailable 安全停止とした。Phase F 完了条件: 前提ゲート通過後に review/Wiki が agy へルートされ、config 戻しでロールバックできることを回帰テストで確認。
disposable git repo で次を実証してから implement の計画追記・実装を行う: .git write 拒否 / git commit・push・reset 拒否 / 対象ファイルのみ編集可 / 許可 test command のみ実行可 / baseline 以降の direct commit 検出 / scope 外変更検出 / timeout 後の子・孫プロセス停止。不合格なら implement の agy 統合は見送り、review/Wiki だけ先行リリース。
v2「テスト」を Phase へ割り当てる。最低限:
fullyIdle=false 失敗 / unknown termination 失敗 / path traversal 拒否 / 別 HOME transcript 拒否 / conversationID 不一致拒否 / malformed JSONL 拒否 / DONE response 無し拒否 / exit0+timeout marker を timeout 扱い / version-hash drift 拒否 / internal status→ResultContract・SummaryResult 変換。BUTLER_STATE_ROOT 解決・permission・cleanup / transcript の symlink escape を is_within_home_resolved で拒否。実機テストは通常 CI から分離し、明示 opt-in で合成入力のみ。
ロールバックは route/config を戻すだけ。agy の回答を既存データへ不可逆反映しない。
ResultContract / Wiki SummaryResult へ正しく変換される。pending を返す。BUTLER_WIKI_SUMMARY_BACKEND 切替)。uv run pytest で 522 passed, 22 subtests(collection error なし)。なお ButlerEnvIsolationTests / MCP stdio facade テストは環境依存で停滞報告あり(本 fixup 環境では isolation 単独 0.5s / facade 全体 13.8s / 全体 34.7s で完走)。pyproject 入口整備で collection が通るようになった結果、従来 collection error で到達しなかった MCP stdio テストが default 実行で走るようになった点に留意(停滞は本差分起因ではない既知の stdio lifecycle 問題)。tests/test_implement_maid.py の route/mock 乖離を解消(production route 切替前ゲート)。
1cfc692a, 2026-06-14)で gemini_cli_implement_maid へ変わり、テストの butler.maids.implement_maid._project_root / generic worker mock が適用されず、temporary repo ではなく実 butler2 repo を対象にする。sandbox 下では実 .git が read-only で branch_setup 失敗(...lock: Read-only file system)。Phase A 起因ではない(#70 以降の既存乖離)が、sandbox が無ければ実 repo に branch 作成・変更し得る安全問題。tests/test_implement_maid.py の setUp で butler.runtime.load_maid_registry / load_intent_routes を patch し、implement intent を必ず ImplementMaid(temp repo + mock worker)へ解決させて production route config に依存しない回帰へ固定。22 件 pass(実 CLI を起動しないため 45s → 1.2s)。pyproject.toml の [tool.pytest.ini_options] で testpaths=["tests"] / norecursedirs=["archive", ...] を設定し、archive/benchmarks の同名 test_counter.py による collection error を解消。uv run pytest が全体 suite を完走する。高位 5 +中位 4 を反映:
pending/retry_after_seconds)へ更新し、internal status → review ResultContract / Wiki SummaryResult の変換表を明記。pending を DoD 化。#73 整合をスキーマ整合テストと定義。BUTLER_STATE_ROOT: B0 として設定名・既定値・owner-only・外部 project 解決・削除 cleanup をタスク化。承認条件の 3 点を修正:
butler/models.py:57 の ResultContract.status は ok/need_input/blocked/failed/cancelled。ユーザー入力不要な quota/concurrency 待ちは pending → blocked(+retry_after_seconds)、need_input は auth_required のみ。Wiki 側は skipped + reason(quota_backoff/concurrency_backoff) + retry_after_seconds に固定。pending。A1〜A5 実装後のレビューを受け、以下を反映:
judge_success の conversation ID 照合が fail-open(transcript に ID 不在だと素通り)だったのを not cids or stop_cid not in cids へ fail-closed 化。extract_done_response(records, conversation_id=stop_cid) で対象 conversation の DONE のみ採用(別 conversation の混入を防止)。is_within_home は字句判定のみと明記し、is_within_home_resolved(Path.resolve() 実体検証)を追加。Phase B の transcript open 直前に必須(B-read タスク・テスト追加)。test_implement_maid.py の route/mock 乖離(#70 以降)を production 切替前ゲートとして明記(別 issue 可)。過去の A0 案は「Phase A の完了条件が後続 B2/B3/B4 に依存する」順序不整合だったため superseded とする。
実バグ 2 件+設計指摘を反映:
run_with_process_group を実行中の並行ドレイン(reader thread)に変更し、大きな正常出力を timeout 誤判定しないように修正。max_output_bytes 超過は output_limit_exceeded で fail-closed。回帰テスト(2MB 正常 / 上限超過)追加。_terminate_process_group を、grace 後に leader の生死で分岐せず group へ無条件 SIGKILL へ修正。SIGTERM を無視する子も停止することを実プロセステストで確認。run_toolless を実装し、lifecycle(auth→deny 検証→version gate before→project lock→global slot→実行→version gate after→secure transcript/Stop event 回収→judge)を順序込みで強制。fake-agy 端到端テスト追加。run_toolless 経由で slot 満杯/project busy の pending も検証。global_concurrency_slot(timeout=None) を project_lock と同じ無期限待機に統一。read_transcript_securely を HOME dir fd 基準の openat+O_NOFOLLOW(中間/最終 component)へ変更し、判定→再 open の差し替えを排除。最終 component symlink 拒否テスト追加。cleanup_project_state(対象 project のみ削除・他に非影響)を実装・テスト。テスト: tests/test_agy_runtime.py 52 件、全体 98 件 pass。
逆境テストで再現された 5 件を修正:
run_with_process_group を drain thread join 後の最終 over を必ず反映するよう修正。max_output_bytes を stdout+stderr の合計上限に統一(_OutputAccumulator)。有限出力の回帰テスト追加。run_toolless で開始時に絶対 deadline を作り、auth/lock/slot/実行へ残り時間だけを渡す。lock/slot 待ちは残り時間で上限し、超過時は lock を解放して pending。process へ渡す outer timeout も残り時間。回帰テスト(lock 占有中に outer deadline で打ち切り)追加。validate_project_key(32 桁 hex 限定)を project_state_dir/initialize_project_home/cleanup_project_state の入口に追加。cleanup は削除直前に resolved path が <state_root>/projects 直下であることも確認。../victim 等を拒否するテスト追加。read_transcript_securely で final fd を fstat し regular file のみ許可、サイズ上限、open/read/decode 失敗を TranscriptAccessError へ統一(runner が failed に変換)。bad-utf8/directory/oversize テスト追加。Popen 直前に再検証(_check_deny_and_fingerprint)。lock 待機中に settings/binary が変化した場合を検出。実行後の fingerprint 比較も維持。並行改ざんの回帰テスト追加。別 session へ逃げる process・settings 配置・Stop hook schema・実 keyring・Overages は計画どおり Phase C の実機ゲートで扱う。
テスト: tests/test_agy_runtime.py 61 件、全体 107 件 pass。
#2284 の 5 件は承認。残り deadline 契約 2 点を修正:
run_toolless を 再検証・request/workspace/command 構築の後、Popen 直前に remaining() を再計算するよう修正。0 以下なら process を起動せず lock/slot を解放して deadline_exceeded の pending。回帰テストで「fake process が起動していない(transcript 未生成)」も確認。remaining() <= 0 なら deadline_exceeded、個別 timeout が先に尽きた場合のみ project_busy / concurrency_backoff と区別。pending の retry_after_seconds は待機上限(concurrency_timeout)の流用をやめ、専用の ToollessRunConfig.retry_backoff_seconds(既定 5.0・常に正)を返す契約に。回帰テスト(deadline_exceeded vs project_busy の区別、retry 値が正)を追加。テスト: tests/test_agy_runtime.py 64 件、全体 110 件 pass。