きっかけは、ありがちな小さな事故でした。リリースから半年ほど経った壁紙アプリの一本で、インタースティシャル広告のフロアが思ったより低いままになっていて、eCPM が前月比で 12% ほど下がっていることに気づいたのです。AdMob 管理画面で個別ユニットを開いてみると、フロアプライスをテスト目的で一時的に下げたまま、Remote Config 側の interstitial_enabled フラグが false になっていて、しばらく配信自体が動いていなかったことが分かりました。設定が二箇所に分散していて、片方をいじったあとにもう片方を戻し忘れる。よくあるドリフトです。
私は 2013年からアプリ事業を続けていて、累計 5,000万 DL ほどになります。広告設定はリリースごとに少しずつ動かす性質のものなので、こうした「気づくのが遅れる差分」をどう拾うかが、個人開発で広告収益を保つときの最大の課題になっていました。Antigravity の Background Agent をリリース監視に使い始めたタイミングで、ついでに AdMob と Remote Config の週次差分レビューも任せてみよう、と決めて 6週間運用したのが今回の記事の題材です。
なぜ「人がやらないジョブ」として切り出したのか
宮大工をしていた両祖父の影響で、私には「丁寧に作ったものは、丁寧に世話をする義務がある」という素朴な感覚があります。リリース直後は熱量が高いので AdMob 管理画面も毎日見るのですが、3ヶ月、半年と経つにつれて、新作開発に集中するために覗く頻度が落ちていきます。落ちた瞬間が、いちばんドリフトが発生しやすいタイミングでした。
Background Agent に投げる仕事は、最初は意識的に小さく区切りました。
- AdMob 管理画面の各広告ユニット設定をスナップショットとして取得する
- Firebase Remote Config の広告関連キー (
*_enabled,*_freq_cap,*_floor等) をスナップショットとして取得する - 前週のスナップショットと差分を取って、変更されたキーと変更してない側の対応キーを並べて表示する
- 「片側だけが変わっている項目」だけを優先度高で抽出する
ここに「改善案を出す」「自動で書き戻す」といったタスクは入れていません。書き戻しの判断は人間がやる、というのが運用の前提です。
実際に動かしている定義の骨格
Agent のジョブは、概ね次の形に落ち着きました。説明のため要点だけ抜き出していますが、実物もこれに近い構成です。
# antigravity/agents/admob-rc-weekly-diff.yaml
name: admob-rc-weekly-diff
schedule: "0 3 * * MON" # 月曜 03:00 JST
tools:
- admob.list_ad_units
- admob.get_ad_unit
- firebase.remote_config_template
- storage.kv # 前週スナップショットの保管庫
steps:
- id: snapshot_admob
run: admob.list_ad_units(app_id=$APP_ID) -> get_ad_unit(*)
- id: snapshot_rc
run: firebase.remote_config_template(project=$PROJECT)
- id: load_prev
run: storage.kv.get("admob_rc_snapshot:last")
- id: diff
run: tools.diff_two_snapshots(current=[snapshot_admob, snapshot_rc], prev=load_prev)
- id: classify
run: tools.classify_pairs(diff, pair_rules=$AD_UNIT_RC_MAPPING)
- id: report
run: tools.render_markdown(diff, classify) -> slack.notify(channel="#ad-ops")
- id: save
run: storage.kv.set("admob_rc_snapshot:last", [snapshot_admob, snapshot_rc])ポイントは pair_rules ($AD_UNIT_RC_MAPPING) です。「広告ユニット名」と「Remote Config のキー」を、サフィックスや命名規約ではなく明示的な対応表として持たせます。命名規約を信じてしまうと、長期的に必ず崩れます。ここに 80 行ほどの YAML を持つことで、Agent は「片側だけ動いた」を確信を持って言えるようになりました。
6週間で実際に拾えたもの
地味ですが、運用上ありがたかった検知は次のとおりです。
- 1週目: Remote Config の
interstitial_freq_capを 5 → 3 に下げたが、AdMob ユニットのフロアプライスを戻し忘れていた (合計で eCPM がしばらく低い水準だった)。 - 3週目: 新規ユニット
rewarded_v2を AdMob に作ったが、Remote Config 側でrewarded_v2_enabledを追加し忘れて、本番では一切配信されていなかった。 - 5週目: A/B テストで Remote Config の比率を 50/50 から 70/30 に変えたあと、片方の Variant の AdMob グループに紐付くメディエーション設定が古いままになっていた。
どれも「ちゃんと見れば気づく」レベルですが、「ちゃんと見るタイミング」を週1で固定したのが効きました。月曜の朝、コーヒーを淹れているあいだに Slack の通知を読む、というだけの儀式です。
ぶつかった壁
楽な話ばかりではありませんでした。
ひとつめは、AdMob API の Remote Config 側との粒度の差です。AdMob のメディエーショングループは Network ごとに細かい設定を持っていて、これを全部スナップショット化すると JSON が 800KB を超えました。差分表示が読めなくなるので、最初の 2週間は「メディエーション詳細は除外、ユニット単位の有効/無効・フロアプライス・freq cap だけ」に絞りました。何が「片側だけ動いた」に値するかは、ジョブを動かしてから決めるしかない、というのが実感です。
ふたつめは、Agent の権限境界です。最初は「Agent に Remote Config を書き戻す権限も与えれば、ドリフトを自動で直してくれる」と考えていました。実際に検証用環境で書き戻しを試しましたが、A/B テスト中の Variant 比率に Agent が踏み込んでしまう事故が一度起き、その時点で「書き込みは絶対に人間」とルールを固めました。Background Agent の便利さは、ちょうど「書かないことに価値がある領域」を可視化してくれる点にもあると考えています。
みっつめは、レポートのノイズです。差分が出ていないときも毎週通知すると、3週目あたりから内容を読まなくなりました。今は「差分が 0 件のときは静かに 1 行だけ、件数がある週は詳細を見る形式に切り替える」という単純な分岐を入れています。
6週間運用して見えた境界線
Background Agent に向く仕事は、私のなかでは次の輪郭になりました。
- 向く: 設定の二箇所同期チェック、リリース後 48〜72h のクラッシュ率監視、レビューの新着拾い、ストアコンソールのコンプライアンス警告通知。
- 向かない: フロアプライスの最適化判断、UI 変更の AB 結論出し、メディエーション順序の自動入れ替え。
向くものに共通するのは「差分を見つける」までで価値が立っていて、「どう動くか」は人間に残しておくべき判断、という構造です。逆に「向かない」側は、その時点での事業フェーズや在庫状況などの外部文脈が必要で、Agent に閉じた知識では責任ある提案ができません。
私のなかでは、これは 17歳のころに教わった「芸術とは、全ての人に開かれた自然な言語であるべきだ」という考え方の、技術側の翻訳でもありました。仕事の言語が誰にでも開かれているとき、その作業は分担できます。逆に文脈に強く依存する判断は、いまのところ作者自身に残しておくほうが結果も誠実さも保てる、という線引きです。
これから取り組むこと
次の 6週間では、対応表の pair_rules を「ユニット 1 つにつき 1 Remote Config キー」ではなく「セット (例: rewarded_v2 ⇄ rewarded_v2_enabled + rewarded_v2_floor + rewarded_v2_freq_cap)」に拡張する予定です。これに合わせて、差分検知も「セット全体としての整合性」を評価できるようにしたいと考えています。
Background Agent は、地味で繰り返しの作業を一定の品質で続けてくれる「もうひとりの自分」のような存在になりつつあります。離れて暮らす子どもたちに、いつか「父さんの仕事はこういう形をしているよ」と説明する日が来たとき、こうしたツールとの付き合い方そのものを丁寧に話せる人でありたい、と静かに思いながら設定ファイルを編集しています。
同じく個人開発で広告収益を保守している方の参考になれば嬉しいです。お読みいただきありがとうございました。