6月18日、Gemini CLI が個人向けと AI Pro / Ultra 向けの応答提供を終えました。私自身、いくつかの定型処理を Gemini CLI に任せていたので、この日に合わせて Antigravity 2.0 のバックグラウンドエージェントへ載せ替える作業をしていました。
ところが、手を動かして最初にぶつかったのは「どう載せ替えるか」ではありませんでした。「そもそも、これをエージェントに任せていいのか」という問いの方でした。Antigravity 2.0 は単一プロンプトから計画・実装・テスト・デプロイまで走り切れますし、複数のエージェントを並行で動かせます。任せられる範囲が広がったぶん、任せてはいけない範囲を自分で決めておかないと、便利さがそのまま事故の確率になってしまいます。
実際、移行初日に一度ヒヤリとしました。あるエージェントが「不要になった生成物を片付ける」流れの中で、消すべきでないキャッシュまで削除候補に並べていたのです。最終実行の手前に確認を挟んでいたので事なきを得ましたが、もしそこを自動にしていたら気づけませんでした。この経験から、任せ方を詰める前に「任せない線引き」を3本だけ言語化しておくことにしました。本稿はその記録です。
なぜ「任せ方」より先に「任せない線引き」を決めるのか
エージェントの自動化は、うまくいっているときほど中身を見なくなります。10回連続で正しく動けば、11回目も見ずに通したくなります。ところが事故は、その「見なくなった11回目」に起こります。
つまり自動化の安全性は、平常時の成功率では決まりません。失敗したときにどこで止まるか、誰が気づくかで決まります。だからこそ、設計の出発点は「どう速く回すか」ではなく「どこで人間に戻すか」に置くべきだと私は考えています。境界線を先に引いておけば、あとは安心して自動化の範囲を広げられます。順序が逆だと、広げた後で怖くなって結局すべてを手動に戻す、という遠回りになりがちです。
境界線1 — 取り返しのつかない操作は、最終実行だけ人間に残す
最初の線引きは、操作を「取り返しがつくか」で二分することです。テストの実行、ビルド、下書きの生成は、失敗してもやり直せます。一方で push、本番デプロイ、削除、公開は、一度走るとロールバックに別の手間がかかります。
私の運用では、エージェントには取り返しのつく操作までを任せ、取り返しのつかない操作は「準備」までで止めてもらいます。エージェントは差分を作り、コミットメッセージを整え、確認用の出力を並べるところまでを担当します。最後に引き金を引くのは人間です。これを口頭ルールにすると必ず形骸化するので、コードで固定します。
# 操作を「取り返しがつくか」で分類し、
# 取り返しのつかない操作はエージェントに最終実行させない
IRREVERSIBLE = {"push", "deploy", "delete", "drop", "publish", "purge"}
def classify(action: str) -> str:
verb = action.strip().split()[0].lower()
return "irreversible" if verb in IRREVERSIBLE else "reversible"
def gate(action: str) -> dict:
kind = classify(action)
if kind == "irreversible":
# エージェントは「準備」までを担当し、最終実行は人間に渡す
return {"action": action, "auto_run": False, "needs_human": True}
return {"action": action, "auto_run": True, "needs_human": False}
if __name__ == "__main__":
for a in ["build site", "git push origin main", "run tests", "deploy production"]:
r = gate(a)
flag = "needs-human" if r["needs_human"] else "auto"
print(f"{flag:12} {a}")
# 出力:
# auto build site
# needs-human git push origin main
# auto run tests
# needs-human deploy production
ここで大事なのは、分類を完璧にしようとしないことです。判定に迷う動詞が出てきたら、迷った時点で irreversible 側に倒します。安全側に倒すコストは「確認が一手間増える」だけですが、危険側に倒すコストは「本番が壊れる」です。両者は釣り合いません。Gemini CLI 停止に伴う依存整理の進め方は自動化の依存を棚卸しする手順にまとめてありますので、移行作業と合わせて参照していただければと思います。
境界線2 — 判断の理由を1文で書けないタスクは渡さない
2本目は、タスクそのものの性質に関する線引きです。私はエージェントに渡す前に、そのタスクの「正解の理由」を1文で書けるかどうかを確かめます。
たとえば「テストが緑なら次へ進む」は、正解の理由が明確です。緑か赤かという観測可能な基準があるからです。一方で「読みやすいように文章を整える」は、何をもって正解とするかが私の頭の中にしかありません。基準が言語化できないタスクをエージェントに渡すと、エージェントは「それらしい何か」を返してきますが、それが私の意図と合っているかを毎回人間が読んで確かめることになります。これでは自動化した意味が薄れます。
判断の理由を1文で書けないタスクは、まだ自分の中で基準が固まっていないサインだと捉えています。そういうタスクは無理に任せず、まず自分で数回やって基準を言葉にし、観測可能な条件に落としてから渡します。この順序を守ることを個人的に強くお勧めします。基準を先に言葉にしてから渡す進め方を推奨します。そうすると、後述する停止条件も自然と書けるようになります。
境界線3 — 失敗の検知コストが実行コストを上回るなら、自分でやる
3本目は、コストの比較です。エージェントに任せる判断は、実行を肩代わりしてもらえる利益と、失敗を見つけるための負担を天秤にかけて決めます。
実行そのものは一瞬でも、その失敗を見つけるのに実行の3倍の時間がかかるタスクがあります。たとえば、複数ファイルにまたがる設定変更を任せた場合、変更自体は速くても「意図しない箇所まで触っていないか」を確かめるレビューに時間がかかります。検知コストが実行コストを上回るなら、最初から自分の手でやった方が結果的に速いのです。
この比較は感覚に頼ると甘くなるので、私はタスクを任せる前に「もしこれが間違っていたら、何分で気づけるか」を自問します。すぐに気づける仕組み(CI の失敗通知、差分の行数上限、鍵の混入チェックなど)が用意できているタスクだけを自動の輪に入れ、それが用意できないタスクは輪の外に置きます。検知の仕組みがないまま任せるのは、目を閉じて自動化するのと同じです。ここを軽視するのが、自動化でよく踏む落とし穴です。先に検知を用意しておけば、この落とし穴は回避できます。
実際にどう振り分けたか
3本の境界線で、移行対象のタスクを実際に振り分けた結果が次の表です。個人開発で複数サイトを並行運用している中での判断なので、規模が違えば線の位置も変わりますが、考え方の参考になればと思います。
| タスク | 任せる度合い | 理由 |
| 記事の下書き生成 | 全部任せる | 取り返しがつく。基準を品質ゲートで観測できる |
| テスト実行・ビルド | 全部任せる | 緑か赤かで結果が明確。失敗は即座に分かる |
| 差分の作成・コミット整形 | 準備まで任せる | push の手前で人間が最終確認する |
| 本番デプロイ・公開 | 引き金は人間 | 取り返しがつかない。境界線1に該当 |
| 不要物の削除・パージ | 候補提示まで | 誤削除の検知コストが高い。境界線3に該当 |
| 文章の「読みやすさ」調整 | 任せない | 正解の理由を1文にできない。境界線2に該当 |
表にしてみて気づいたのは、「全部任せる」と「任せない」の二択ではなく、「準備まで任せる」という中間が一番多かったことです。エージェントの価値は、最後の判断を奪うことではなく、判断の直前までを整えてくれるところにあるのだと感じています。
停止条件をコードに落とす — 「完了」をエージェント任せにしない
境界線を引いたら、最後に「完了の定義」を自分の手元に取り戻します。バックグラウンドエージェントで一番危ないのは、エージェント自身が「終わりました」と報告してくる点です。その報告を鵜呑みにすると、満たしていない条件を見落とします。
そこで、完了を自己申告ではなく観測可能な条件で判定します。すべての停止条件を満たして初めて「完了」とし、1つでも欠ければ人間のレビュー待ちに戻します。
# 「完了」をエージェントの自己申告に任せず、
# 観測可能な条件で判定する
from dataclasses import dataclass
@dataclass
class StopConditions:
tests_passed: bool # CI が緑か
diff_within_budget: bool # 変更行数が想定の範囲内か
no_new_secrets: bool # 鍵やトークンの混入がないか
def is_done(c: StopConditions) -> bool:
# すべての条件を満たして初めて「完了」とみなす
return c.tests_passed and c.diff_within_budget and c.no_new_secrets
def review(c: StopConditions) -> str:
if is_done(c):
return "done"
# どれか1つでも欠ければ、欠けている条件を添えてレビュー待ちに戻す
missing = [k for k, v in c.__dict__.items() if not v]
return "needs_review: " + ", ".join(missing)
state = StopConditions(
tests_passed=True,
diff_within_budget=False,
no_new_secrets=True,
)
print(review(state))
# 出力: needs_review: diff_within_budget
このパターンの良いところは、完了の基準が「私の主観」ではなく「再現可能な条件の集合」になることです。条件が増えても減っても、コードを読めば何をもって完了としているかが一目で分かります。エージェントの完了報告をどう検証するかはバックグラウンドエージェントの完了をどう確かめるかでも掘り下げていますし、使い捨てワーカーにコスト上限を持たせる設計は一時ワーカーにコスト上限を設けるが参考になります。
運用して見えた誤算
2か月ほどこの3本の境界線で運用してみて、想定と違った点が3つありました。
-
「準備まで任せる」が増えると、確認の回数も増える。 安全にはなりますが、人間の確認がボトルネックになります。私はこの対策として、確認をまとめて1日2回の時間帯に寄せ、その間はエージェントに準備を貯めてもらう形に変えました。
-
境界線2で弾いたタスクが、基準を言語化すると任せられるようになる。 「読みやすさ調整」も、観点を「1文を40字以内に」「受け身を3回以上使わない」のように観測可能な条件へ分解すると、境界線の内側に入ってきました。線は固定ではなく、自分の理解が深まると動きます。
-
検知の仕組みを作る時間を、実行の自動化と同じくらい見積もる必要がある。 当初は実行を自動化すれば終わりだと思っていましたが、実際には「失敗にすぐ気づく仕組み」を作る方に時間がかかりました。ここを軽く見ると、境界線3が機能しません。
振り返ると、エージェントを増やすことよりも、止まる場所を正しく設計することの方が、結果として安心して任せられる範囲を広げてくれました。
次の一歩
まずは自分のパイプラインのタスクを紙に書き出し、それぞれに「取り返しがつくか」「正解の理由を1文で書けるか」「失敗に何分で気づけるか」の3つを記入してみてください。この3列を埋めるだけで、どこに境界線を引くべきかが見えてきます。コードに落とすのはその次で十分です。
私自身もまだ運用しながら線を引き直している途中ですが、同じように自動化の範囲で迷っている方の判断材料になれば嬉しいです。お読みいただきありがとうございました。