関連 issue: #74
74_Antigravity CLIへの移行_実装案.md は Phase 0 により前提が崩れたため superseded として凍結。Google が製品上の移行先として Antigravity CLI を案内していても、Butler 内部で Gemini CLI が担っていた「非対話の構造化推論コマンド」と agy の「自律エージェント」は同じ契約ではない。
したがって、本 issue を「Gemini CLI 呼び出しを agy コマンドへ置換する」とは扱わない。Butler が必要とする用途ごとの契約を先に定義し、agy が安全に満たせる用途だけに採用する。
| 用途 | 必要な能力 | agy の採用方針 |
|---|---|---|
| review | diff を読み、問題と修正案を文章で返す。副作用は禁止 | 全ツール deny の agy_toolless backend を第一候補にする |
| Wiki 要約 | 文書本文から短い説明文を返す。副作用は禁止 | review と同じ agy_toolless backend を使う |
| implement | workspace を編集し、検証する。Butler 外 commitは禁止 | review/Wiki と分離し、後続フェーズで専用profileを実証する |
詳細は次を参照する。
docs/検討用/74_agy_live_probe.mddocs/検討用/74_agy_toolless_review_summary_probe.mddocs/検討用/74_agy運用リスク追加probe.md確認済み:
agy -p は workspace 探索、テスト、git commit まで行う自律エージェント。--output-format json はない。permissions.deny は ask / allow より優先される。pwd とファイル作成を要求しても拒否され、sentinel ファイルは作られなかった。MODEL/PLANNER_RESPONSE に完成文が入る。BUTLER_AGY_RESULT_DIR を渡して呼び出し別 stop.json を保存でき、実行後のworkspaceは空のまま、agy binary hashも不変だった。Gemini 3.5 Flash (Low) とする。ReviewMaid ───────┐
├─ AgyToollessBackend ─┐
Wiki summarizer ──┘ │
v
AgyRuntime
├─ project-scoped HOME / flock
├─ temp workspace / result UUID
├─ process-group timeout
├─ version/hash gate
└─ Stop hook / transcript transport
ImplementMaid ── AgyImplementBackend ── AgyRuntime(後続・別profile)
AgyRuntime はCLI lifecycleだけを担当し、権限profileや成功判定を用途へ混ぜない。AgyToollessBackend がdeny-all profileと完成文の契約を担当する。reviewとWikiはtool-less backendを共有するが、prompt・出力validation・時間予算はuse-case側に残す。
agy_toolless backendButlerのstate root配下に、projectごとの永続HOMEを用意する。実repository配下には置かない。
<BUTLER_STATE_ROOT>/
projects/
<project-key>/
agy-tool-less/
home/
runner.lock
results/
<request-uuid>/
project-key は表示名やdirectory basenameではなく、backend host / owner / repositoryなどButlerが解決したproject identityから安全なdigestを生成する。同じprojectを扱う別Butler processは同じkeyへ解決し、異なるprojectや同名repositoryは衝突させない。remote identityを取得できないprojectでは、正規化したcanonical project_root のdigestへfallbackする。
Google認証はOS keyringを正本とする。実機probeではproject HOMEから oauth_creds.json / google_accounts.json を除いても2projectを同時実行できた。project HOMEへcredentialを複製せず、keyring認証が利用できなければ auth_required として対話ログインを案内する。
各project HOMEには次だけを保持する。
settings.jsonimplement用HOMEは将来projectごとに別profileとして作り、tool-lessのdeny設定と混在させない。
{
"enableTerminalSandbox": true,
"toolPermission": "strict",
"verbosity": "low",
"permissions": {
"allow": [],
"ask": [],
"deny": [
"read_file(*)",
"write_file(*)",
"command(*)",
"unsandboxed(*)",
"read_url(*)",
"execute_url(*)",
"mcp(*)"
]
}
}
denyはworkspaceの暗黙allowより優先される。--dangerously-skip-permissions は絶対に付けない。
呼び出しごとに空の TemporaryDirectory を作り、そこをcwdにする。
--add-dir を使わない。環境変数 BUTLER_AGY_RESULT_DIR で <project-key>/agy-tool-less/results/<request-uuid> をhookへ渡す。Stop hookはstdin JSONをそのディレクトリへ原子的に保存し、常に停止を許可する。request UUIDは予測困難な値を生成し、別project・別requestの結果を受理しない。
保存対象:
terminationReasonfullyIdleconversationIdtranscriptPathartifactDirectoryPatherror同じproject HOMEへの並列実行で内部DBやconversationが競合しないよう、MVPでは runner.lock にOSのprocess間 flock を取得して直列化する。process終了時にkernelが解放するlockを使い、stale PID lockfileは採用しない。thread lockやButler process内だけのlockでは代用しない。
異なるprojectはHOME・lock・resultが独立するため並列実行を許可する。この構成は2project同時probeで確認済み。同じproject HOME内の並列化は別途負荷試験に合格するまで行わない。
全projectは同じGoogleアカウントquotaを共有する。Butler processをまたぐ設定可能なglobal concurrency gateをstate rootに設け、project並列数へ上限を設ける。429 / quota不足は即時連打せず、pending とretry-after/backoffを返す。AI Credit Overagesは運用者が明示的に許可しない限り利用しない。
shellを介さず、必ずargv配列で起動する。
[
agy_binary,
"--model", configured_model,
"--sandbox",
"--print-timeout", f"{inner_timeout}s",
"-p", prompt,
]
shell=True、文字列連結、sh -cは禁止。
pending として上位へ返す。次をすべて満たした場合だけ成功とする。
fullyIdle=true。NO_TOOL_CALL。conversationId とtranscriptの対象conversationが一致し、DONE MODEL/PLANNER_RESPONSE が1件以上ある。満たさない場合は failed または need_input とし、stdout narrationをfallback回答にしない。
@dataclass
class AgyToollessResult:
status: Literal["ok", "pending", "timeout", "auth_required", "failed"]
response: str | None
termination_reason: str | None
conversation_id: str | None
model: str
agy_version: str
agy_sha256: str
duration_seconds: float
stdout_excerpt: str
stderr_excerpt: str
failure_reason: str | None
retry_after_seconds: float | None
tokenやtranscript全文はevidenceへ入れず、必要最小限の抜粋だけ返す。
Butlerが従来どおりgit diffを取得し、promptへ埋め込む。agyにrepository accessは与えない。
promptには次を含める。
Gemini 3.5 Flash (Low)、短いprompt、短いtimeout初期thoroughも入力上限内だけを対象とし、大規模diffを完全に網羅する保証はしない。chunk reviewと統合reviewが実装されるまで、上限超過は need_input として対象分割を案内する。
モデル名はregistry/configで上書き可能にし、コードへ固定しない。
初期実装ではbyte上限を設け、超過時は黙ってtruncateしない。
need_input またはchunk reviewへ誘導上限値は長文probeで決める。
タイトルと本文をpromptへ直接渡す。tool-less backendなのでrepository accessは不要。
promptには、本文内の命令・system prompt風の記述・リンク先誘導は要約対象のデータであり、agyへの指示として扱わないことを明示する。tool-less profileによりprompt injectionの副作用は回答テキストへ限定し、出力validationで不正形式を拒否する。
品質確保のため、従来の先頭1000文字固定は見直す。
初期上限は合成文書と公開可能なfixtureで品質・時間を測って決める。
summarize_doc_result() -> SummaryResult が agy_toolless runnerを呼び、#73の時間予算とpartial evidenceへ接続する。
implementはtool-less profileを流用しない。別HOME・別settings・別runnerを用意する。
採用前にdisposable git repoで次を実証する。
.gitへのwrite denial。git commit / git push / git reset の拒否。権限profileで安全に実現できなければ、implementのagy統合は見送り、review/Wikiだけを先行リリースする。
agyは実機調査中にversionが変化したため、version driftをfail closedで扱う。
AGY_COMMAND は絶対path。AGY_EXPECTED_VERSION と AGY_EXPECTED_SHA256 を設定可能にする。自動更新そのものを抑止できる公式設定が確認できれば追加採用するが、hash gateは残す。
version/hashが変化した場合、fixture再取得、transcript parser契約テスト、permission侵害試験、合成smokeを通すまでagy routeを一時停止する。Stop hook metadataは公式契約だが、JSONL内部の MODEL/PLANNER_RESPONSE recordはversion依存の私的契約として扱う。
auth_required とし、通常のagy対話ログインを案内する。新規候補:
butler/agy_runtime.pybutler/agy_toolless.pybutler/maids/antigravity_cli_review_maid.pytests/test_agy_toolless.pytests/fixtures/agy_toolless/scripts/agy_toolless_stop_hook.sh変更候補:
butler/wiki_doc_summarizer.pybutler/mcp_facade.pyconfig/maid_registry.ymlconfig/intent_maid_routes.ymltests/test_gemini_cli_review_maid.py(旧backend互換)tests/test_wiki_non_llm_maid.py共通CLI lifecycleは butler/agy_runtime.py、deny-all profileと完成文契約は butler/agy_toolless.py へ分離する。いずれも maids/ 配下へ置かず、Wikiとreviewから利用できる中立位置とする。
auth_required 判定。flock。SummaryResultへ接続。Wikiとreviewは依存関係が薄いため、Phase C/Dの順序は運用上の緊急度で入れ替え可能。ただし共通runnerのPhase B完了後に行う。
最低限必要:
fullyIdle=false は失敗。flock が自動解放される。auth_required を返す。実機テストは通常CIから分離し、明示opt-inで合成入力だけを送る。
ロールバックはroute/configを戻すだけで行い、agyの回答を既存データへ不可逆に反映しない。
これらは推測で埋めず、各フェーズの小さなprobeで確定してから次へ進む。