ANTIGRAVITY LABEN
記事一覧/Antigravity 基本
Antigravity 基本/2026-07-31上級

たまに落ちるフックが毎回落ちるようになった — Antigravity SDK の宣言的セッション定義への移行

初期化後の動的なフック登録が廃され、セッション生成時の宣言へ寄せられました。個人開発の4つのエージェント定義を移した記録と、登録順の非決定性を2,000回測ったハーネスをまとめています。

Antigravity345SDK5フック設計移行手順再現性5

プレミアム記事

移行を終えた翌朝、CI が赤いまま固定されていました。

前日までは、10回に4回ほどは通っていたテストです。それが移行後は一度も通らない。手を入れた覚えのある箇所は、フックの登録方法だけでした。

先に結論を書きます。これは移行の失敗ではありませんでした。むしろ、移行が効いたことの証拠でした。

変わったのは「何を登録できるか」ではなく「いつ決まるか」

Antigravity SDK では、セッションを初期化したあとに動的にフックとトリガーを登録する方式が廃され、セッション生成時の宣言へ寄せられました。版によって細部は動きますので、実際の記述は Antigravity の changelog でご確認いただければと思います。

登録できる内容そのものは変わっていません。変わったのは、構成が確定するタイミングです。

観点初期化後の動的登録セッション生成時の宣言
構成が確定する時点実行中(登録が終わった順)セッション生成の瞬間
フックの実行順登録完了の順に依存する書いた配列の順
条件分岐の置き場所登録するかどうかで表現できるフックの内部で表現する
同じ入力に対する再現性実行ごとに変わりうる常に同じ
構成の検査実行してみないと分からない起動前に静的に読める

私はこの表の最終行のために移行した、と思っていました。起動前に構成を読めることの価値は分かりやすいからです。実際に効いたのは、その1つ上の行でした。

移行対象を機械的に洗い出す

最初は grep -rn "register_hook" で足りると考えておりました。個人開発で持っているエージェント定義は4つだけです。目視でも追えるだろう、と。

足りませんでした。register_hook が条件分岐の中にあるのか、ループの中にあるのか、try の中にあるのかで、書き換えの手間がまったく違ったからです。grep は行を返しますが、その行が置かれている文脈は返してくれません。

そこで、呼び出し位置とその構文的な文脈をまとめて出す小さなスクリプトを書きました。

#!/usr/bin/env python3
"""初期化後のフック/トリガー動的登録を洗い出し、移行の難易度で仕分ける。"""
import ast, os, sys
 
DYNAMIC = {"register_hook", "register_trigger", "add_hook", "add_trigger"}
 
def scan(root):
    rows = []
    for dp, _, fs in os.walk(root):
        if any(seg in dp for seg in (".venv", "node_modules", "__pycache__")):
            continue
        for f in fs:
            if not f.endswith(".py"):
                continue
            p = os.path.join(dp, f)
            try:
                tree = ast.parse(open(p, encoding="utf-8").read(), p)
            except SyntaxError as e:
                # 移行途中のファイルが混ざると必ずここに来る。黙って飛ばさない
                print(f"skip (syntax): {p}: {e}", file=sys.stderr)
                continue
 
            # ast には親参照がないため、先に子→親の対応表を作っておく
            parent = {}
            for n in ast.walk(tree):
                for c in ast.iter_child_nodes(n):
                    parent[c] = n
 
            for n in ast.walk(tree):
                if not (isinstance(n, ast.Call) and isinstance(n.func, ast.Attribute)):
                    continue
                if n.func.attr not in DYNAMIC:
                    continue
                ctx, cur = set(), parent.get(n)
                while cur is not None:
                    if isinstance(cur, ast.If):
                        ctx.add("conditional")
                    elif isinstance(cur, (ast.For, ast.While, ast.comprehension)):
                        ctx.add("loop")
                    elif isinstance(cur, (ast.Try, ast.ExceptHandler)):
                        ctx.add("try")
                    cur = parent.get(cur)
                rows.append((p, n.lineno, n.func.attr, sorted(ctx)))
    # ast.walk は幅優先なのでソース順にならない。ここで必ず並べ直す
    return sorted(rows, key=lambda r: (r[0], r[1]))
 
TIER = {
    frozenset(): "A: そのまま配列へ移せる",
    frozenset({"conditional"}): "B: フック内の早期 return へ書き換える",
    frozenset({"loop"}): "B: 生成した関数を配列へ展開する",
    frozenset({"try"}): "C: 失敗時の挙動を先に決める",
}
 
def main(root):
    rows = scan(root)
    for p, line, call, ctx in rows:
        tier = TIER.get(frozenset(ctx), "C: 手作業で判断する")
        print(f"{p}:{line}  {call:<17} ctx={','.join(ctx) or '-':<12} {tier}")
    print(f"\n合計 {len(rows)} 箇所")
 
if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else ".")

ast.walk が幅優先で走るため、そのまま出力するとソース順に並びません。移行作業はファイルを上から順に潰していくものですから、ここで並べ直しておかないと手戻りが出ます。最初にこれで一度混乱しました。

手元の定義に当てたときの出力です。

sample/agents/build_agent.py:4  register_hook     ctx=-            A: そのまま配列へ移せる
sample/agents/build_agent.py:6  register_hook     ctx=conditional  B: フック内の早期 return へ書き換える
sample/agents/build_agent.py:7  register_trigger  ctx=-            A: そのまま配列へ移せる
sample/agents/review_agent.py:5  register_hook     ctx=loop         B: 生成した関数を配列へ展開する
sample/agents/review_agent.py:6  register_trigger  ctx=-            A: そのまま配列へ移せる

合計 5 箇所

A が3件、B が2件。数としては小さいのですが、この仕分けがあると「今日はどこまで進むか」の見積もりが立ちます。C(try の中の登録)が出た場合だけは、書き換えの前に「登録に失敗したら止めるのか、続けるのか」を決める必要があります。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
初期化後の register_hook / register_trigger を AST で洗い出し、移行の難易度をA・B・Cへ仕分けるスクリプト(そのまま自分のリポジトリに当てられる完全版)
動的登録では2,000回中48.2%でしかフックの依存順が満たされていなかったという実測と、それを測った最小ハーネス
宣言へ移すと不具合は消えず「毎回再現する」ようになるという逆転。移行直後にCIが赤で固定されたときの読み方
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

Antigravity 基本2026-06-24
Antigravity 4サーフェスを1案件で組み合わせる — SDKで自作エージェントを動かすまで
Antigravity 2.0・CLI・IDE・SDK を1つの案件の中でどう使い分け、どう橋渡しするか。設計の発散から本番の収束、そして Python SDK で小さなカスタムエージェントを動かすところまでを、実際の運用フローに沿って実装込みで解説します。
Antigravity 基本2026-06-24
Antigravity 2.0・CLI・IDE・SDK — 4つのサーフェスを実プロジェクトで束ねる
Antigravity 2.0(デスクトップ)・CLI・IDE・SDK の4つの入り口を、選び方の基準だけでなく、1つのプロジェクトの中で実際にどう束ねて回すかまで掘り下げます。ヘッドレス実行を自動化に組み込むコード例と、コストと移行の落とし穴も添えました。
Antigravity 基本2026-04-17
google-genai SDK で Antigravity を使う:Python 実践クイックスタート
google-genai SDK を使って Antigravity を Python で呼び出す実践ガイド。インストールからテキスト生成・マルチモーダル・ストリーミングまでコード付きで解説。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →