広告の設定を 1 つ動かすたびに、翌週の数字を見て胃が縮む。長く運用している壁紙アプリで、私の時間をいちばん吸っていたのはこの往復でした。AdMob の管理画面で App Open とインタースティシャルとリワードの頻度をわずかに動かす。Firebase Console で Day 1 リテンションを見る。eCPM を見る。IAP のコンバージョンを見る。機能を書いている時間より、この監視と微調整に費やす時間のほうが長い月すらありました。
ここ数か月、Antigravity の Agent と Firebase Remote Config を組み合わせて、この作業を「リリースを伴わない、小さく安全な微調整ループ」に置き換える試みを続けてきました。動くようになった部分には確かな手応えがあり、同時に「ここは Agent に渡さない」と決めた線もはっきりしてきました。組んだ構成、悪手を 40 時間放置しないための自動ロールバック、そして 6 か月ぶんの実測値を順に書いていきます。新規開発というより、既に AdMob で実収益が出ているアプリの保守・最適化に寄った内容です。
起点:Day 1 が 2% 落ちたとき、原因が分からなかった
きっかけは去年の秋のリリースでした。App Open 広告の表示頻度を「セッションあたり1回」から「Day 0 のみ抑制、Day 1 以降は無制限」に切り替えたところ、翌週から Firebase Analytics の Day 1 リテンションが 33% から 31% に落ちました。アプリ全体の DAU は数十万のスケールなので、2 ポイントの差は無視できません。
指標 変更前(1週間平均) 変更後(1週間平均) 差分
Day 0 → Day 1 リテンション 33.4% 31.2% -2.2pt
インタースティシャル eCPM $4.12 $4.05 -1.7%
App Open eCPM $2.85 $3.91 +37.2%
1日あたり広告売上 $1,820 $1,910 +4.9%
短期の売上は伸びました。問題は、リテンション低下が長期的に LTV を削り、3 か月後には広告売上が逆転して落ち始めたことです。当時はリリースのなかで他にも3つ変更を加えていたので、どれが効いたのかすら切り分けられず、結局アプリ全体を 1 つ前のバージョンに戻して、AdMob の管理画面で広告ユニットを手動でロールバックしました。アプリストア審査を待っていた 3 日間で、推定 $5,000 ほど機会損失が出た計算です。
このとき個人開発者として痛感したのは、広告関連の判断は「アプリのバージョンと完全に切り離せたほうがいい」ということでした。バージョンに紐付いたまま広告ロジックを変えると、いざ戻したくなったときに App Store / Google Play の審査時間ぶんだけ判断が遅れます。Firebase Remote Config に出会ったのはこの直後でした。
なぜ Remote Config だけでは足りなかったか
Remote Config 自体は導入してすぐに馴染みました。広告ユニット ID、表示頻度、Day0 抑制フラグ、リワードの倍率といった「動かしたい変数」を Remote Config に逃がし、アプリ側は起動時に fetchAndActivate() で受け取って AdMob ラッパーに渡すだけです。リリースを挟まずに値を変えられるようになり、ロールバックも「前回値に戻す」だけで済みます。
問題は、「どの値に動かすべきか」を決める頭脳の部分が、引き続き私の手作業として残ったことでした。具体的には、
AdMob 管理画面で eCPM 推移を見る
Firebase Console で Day 1 リテンションを見る
Google Play Console / App Store Connect のクラッシュとレビューを確認する
それらを総合して「今日は App Open の頻度を 1.0 → 0.7 倍に絞る」のような判断を下す
Remote Config を更新する
この一連の動きを毎朝 30〜45 分続けるのは、個人開発の他の作業時間を確実に侵食します。複数アプリを並行運用していると、休日が広告監視で潰れていく感覚すらありました。Antigravity の Agent をここに差し込めないか、というのが本稿のテーマです。
設計の前提:Agent に任せる範囲を狭く切る
最初に明確にしたのは、Agent には判断の全権を渡さない、という原則です。Remote Config は本番ユーザに直接届く設定なので、Agent が暴走すれば全ユーザの体験が一度に壊れます。私が決めた境界はこうです。
Agent が触ってよいパラメータは Remote Config の「動的調整可」マークが付いた変数だけ
値の変動幅は元値の ±15% 以内、かつ事前に決めた絶対上限・下限を超えない
適用範囲は「最大 10% のユーザセグメント」を上限とする(Remote Config の条件で制御)
Agent が判断材料にできるのは Firebase Analytics・AdMob・Crashlytics のメトリクスのみ。レビュー本文の感情判定で値を動かすのは禁止
この境界を「Antigravity Agent への運用ガードレール」として AGENTS.md にも書き、Agent が読み込めるようにしました。AGENTS.md の該当セクションを抜き出すとこうなります。
## Remote Config 自動調整の制約
このリポジトリの Remote Config キーのうち、 `tunable.` プレフィックスを持つキー
だけが Agent による自動調整の対象です。 `fixed.` プレフィックスのキーは絶対に
書き換えてはいけません。
- 1 回の調整で変更してよいキーは最大 2 個
- 値は元値からの相対変動率 ±15% 以内
- 適用ユーザは「audience: opt_in_experiment」(全体の 10%)に限定
- 1 日に同一キーへの変更は最大 1 回
- 直近 24 時間で Crashlytics の crash-free users が 99.0% を下回っている場合、
自動調整は一切実行しない(人間の確認が必要)
ここまで書いてようやく、Agent に「データを見て判断する」役割を渡せる土台ができました。次にやったのは、判断ループを Antigravity Workflow に落とすことです。
オンデマンド最適化ループの全体像
Antigravity の Agent を組み込んだ最適化ループは、次の 6 つのステップを毎日 1 回、深夜 2 時に走らせる構成にしています。深夜帯にしたのは、Remote Config の変更が一晩寝かせた後に確認できるよう、私の起床時刻にちょうど指標が出揃うようにするためです。
Firebase Analytics と AdMob から直近 7 日のメトリクスを取得(BigQuery エクスポート + AdMob API)
Crashlytics の crash-free users を取得し、99.0% を下回っていたらここで終了
直近 24 時間の Remote Config 変更履歴を取得(Firebase Remote Config REST API)
Antigravity Agent に上記データを渡し、「次に動かすべきキーと値」を最大 2 件提案させる
提案を AGENTS.md のガードレールで検証し、違反していたら破棄
残った提案を Remote Config に書き込み、変更内容を Slack に通知
最初は Step 4 を Agent に任せた直後にすぐ Step 6 まで行く構成にしていたのですが、明らかに筋の悪い提案が紛れることがあり、Step 5 を独立した検証ステップに分離しました。Agent の提案を別の小さなコードで機械的に検証する、という構成は今のところ非常に有効です。
// scripts/admob-tuning/validate-proposal.ts
import { AGENTS_GUARDRAIL } from "./guardrail" ;
type Proposal = {
key : string ;
current_value : number ;
proposed_value : number ;
reason : string ;
};
export function validateProposal ( p : Proposal ) : { ok : boolean ; reason ?: string } {
if ( ! p.key. startsWith ( "tunable." )) {
return { ok: false , reason: `non-tunable key: ${ p . key }` };
}
if ( ! AGENTS_GUARDRAIL .allowedKeys. includes (p.key)) {
return { ok: false , reason: `key not in allowedKeys: ${ p . key }` };
}
const delta = Math. abs (p.proposed_value - p.current_value) / p.current_value;
if (delta > 0.15 ) {
return { ok: false , reason: `delta ${ ( delta * 100 ). toFixed ( 1 ) }% > 15%` };
}
const bounds = AGENTS_GUARDRAIL .bounds[p.key];
if (p.proposed_value < bounds.min || p.proposed_value > bounds.max) {
return { ok: false , reason: `out of bounds: ${ bounds . min }〜${ bounds . max }` };
}
return { ok: true };
}
このコードは Agent には書かせず、私が手で書いています。判断は Agent に任せても、判断の検証は人が書いた決定論的なコードに通す。この役割分担をはっきりさせると、本番運用での安心感が一段違いました。
メトリクス取得の落とし穴
Firebase Analytics を BigQuery にエクスポートしている方は分かると思うのですが、デフォルトでは前日のデータが朝の数時間で揃います。最適化ループを深夜 2 時に走らせる場合、参照できるのは「昨日のデータ」ではなく「一昨日のデータ」になります。これは認識しておかないと、Agent が「直近の変更の影響が見えていない状態で次の提案を出す」事故が起きます。
私は Agent に渡すコンテキストの先頭に、必ず次のような注記を入れています。
データの新鮮さに関する注意:
- Firebase Analytics: 直近24時間のデータは含まれない(BigQuery export の仕様)
- AdMob API: 当日のデータは午後に確定するため、直近12時間は含まれない
- Crashlytics: 概ねリアルタイム(直近1時間程度の遅延)
したがって、本日 02:00 時点で参照できる Analytics データは 5/19 までです。
昨日(5/20)行った Remote Config 変更の効果は、この時点ではまだ判定できません。
昨日の変更がある場合は「効果未測定」として、新たな提案を保留してください。
この注記を入れてから、Agent が「変更直後に再度変更する」という最も危ない行動を取らなくなりました。プロンプトの設計で防げる事故と、コードで防ぐべき事故を切り分けることは、Agent を本番に組み込むうえでとても重要です。
Antigravity Workflow への落とし込み
ここまでのループを、Antigravity の Workflow 機能で 1 ファイルに束ねます。私は .antigravity/workflows/admob-nightly.yaml に次のように書いています。
name : "AdMob nightly tuning"
schedule :
cron : "0 2 * * *"
timezone : "Asia/Tokyo"
variables :
app_id : "com.dolice.wallpaper.zen"
guardrail_path : "scripts/admob-tuning/guardrail.ts"
steps :
- id : fetch_metrics
run :
command : "pnpm exec tsx scripts/admob-tuning/fetch.ts"
env :
TARGET_APP_ID : "${{ variables.app_id }}"
timeout : 120
- id : crash_gate
run :
command : "pnpm exec tsx scripts/admob-tuning/crash-gate.ts"
on_failure : abort_workflow
- id : agent_proposal
agent :
profile : "tuning-strategist"
input_from : "fetch_metrics"
max_iterations : 4
timeout : 300
- id : validate
run :
command : "pnpm exec tsx scripts/admob-tuning/validate.ts --proposal=${{ steps.agent_proposal.output_path }}"
on_failure : skip_remaining
- id : apply_remote_config
run :
command : "pnpm exec tsx scripts/admob-tuning/apply.ts --proposal=${{ steps.validate.output_path }}"
- id : notify
run :
command : "pnpm exec tsx scripts/admob-tuning/notify-slack.ts --result=${{ steps.apply_remote_config.output_path }}"
agent.profile で参照している tuning-strategist は、Antigravity 側に登録した Agent プロファイルです。プロファイル定義の側で「使ってよいツール」「読んでよい AGENTS.md のセクション」「期待する出力フォーマット」を制約します。
# .antigravity/agents/tuning-strategist.yaml
name : "tuning-strategist"
description : "AdMob と Firebase メトリクスを読んで Remote Config の調整案を出す"
allowed_tools :
- read_file
- run_shell : [ "pnpm exec tsx scripts/admob-tuning/fetch.ts --help" ]
context_files :
- "AGENTS.md#remote-config-自動調整の制約"
- "scripts/admob-tuning/playbook.md"
output_schema : "scripts/admob-tuning/proposal.schema.json"
max_proposals : 2
output_schema を強制することで、Agent の自然言語の説明と一緒に必ず構造化 JSON が返ってきます。この JSON が次の validate ステップに渡され、機械的に検証されます。Agent の出力を「自由形式の文章」のまま下流に流すと、必ずどこかで形式の崩れが起きます。Antigravity の Workflow で完結させるなら、各ステップの境界で構造化フォーマットを強制するのが、半年運用して得た一番の知見でした。
「触ってよい比率」をどう決めるか
ここまで仕組みの話を書いてきましたが、運用上もっとも難しいのは「ユーザの何 % まで自動調整の対象にしてよいか」という比率の設計です。私の現在の設計は次のとおりです。
アプリ全体のうち 90% は手動で固定した値(fixed バケット)
残り 10% を Agent による自動調整の対象(experiment バケット)
experiment バケットの中で、さらに condition で 2 つのサブバケットに分け、片方は「ベースライン保持」(直近 7 日の平均値を維持)、もう片方は「Agent 提案を適用」
experiment バケット 10% という比率は、当初 30% にしていたのを段階的に下げた結果です。30% で運用すると、Agent が立て続けに値を動かしたタイミングで、全体の eCPM が 5〜8% 単位で揺らぐことがありました。10% に下げてからは、揺らぎは全体の 1% 以内に収まり、もし提案が悪手だった場合の影響範囲も読める規模に収まっています。
個人開発で AdMob を回している場合、収益は予測可能性が極めて重要です。eCPM の月間ボラティリティが大きいと、月次の積立や次の制作費の計画が立てにくくなります。私は「収益の月間ボラティリティを 5% 以内」という制約を運用上の北極星にしていて、experiment バケットの比率はこれを超えない範囲で決めています。
eCPM と Day 1 リテンションを同時に守る判断軸
Agent に渡すプレイブック(playbook.md)には、次のような判断軸を文章で書いています。
## 判断軸の優先順位
1. crash-free users が 99.0% を下回っているなら、いかなる調整も提案しない
2. Day 1 リテンションが直近 7 日平均より -1.5pt を超えて低下しているなら、
広告表示頻度を減らす方向(マイナス側の調整)のみ提案する
3. Day 1 リテンションが安定(直近 7 日平均の ±0.5pt 以内)かつ eCPM が直近 14 日
最低値を下回っているなら、フォーマット切り替え系のキーのみ提案する
4. 上記いずれにも該当しない場合は「提案なし」を返す
## 禁止されている提案
- リワード広告の倍率を 1.5 倍より高くする
- App Open の頻度をセッションあたり 1.5 回より高くする
- Day 0 抑制フラグを false に切り替える(Day 0 はリテンション最重要日)
判断軸の優先順位を明示すると、Agent の提案がぶれにくくなります。注意したいのは、判断軸は AGENTS.md ではなくプレイブックに書き、頻繁に更新する想定にしておくことです。AGENTS.md は「絶対に守るべきガードレール」、playbook.md は「現時点の戦略」と役割を分けると、改善のサイクルが回しやすくなります。
Slack 通知の設計:判断と意思を分離する
最後に、Slack 通知の設計だけ補足します。Agent が提案して Remote Config に書き込んだ内容は、必ず Slack に流して、私の朝の最初の 5 分で目を通せるようにしています。通知本文の構成はこうです。
[AdMob nightly tuning] 2026-05-21 02:14 JST
📊 評価したメトリクス(5/13〜5/19)
- Day1 retention: 32.8% (-0.3pt vs 7d avg)
- Interstitial eCPM: $4.07 (-2.4% vs 14d min)
- App Open eCPM: $3.21 (-5.6% vs 14d min)
- Crash-free users: 99.62%
🤖 Agent 提案(2 件)
1. tunable.interstitial_frequency_cap: 4 → 3 (-25.0%)
理由: eCPM が 14d 最低値を下回り、頻度を下げて単価を回復させたい
→ 却下: 変動幅 25% が 15% 上限を超過
2. tunable.app_open_min_interval_sec: 30 → 35 (+16.7%)
理由: App Open eCPM が下落傾向。表示間隔を広げて単価を維持
→ 却下: 変動幅 16.7% が 15% 上限を超過
✋ 適用された変更: 0 件
💡 メモ: 今回は両提案ともガードレール超過で却下されました。
連続却下が 3 日続いたら、playbook.md の判断軸を見直してください。
私はこの通知を見て、必要に応じて手動で Remote Config を動かすか、playbook.md の判断軸を緩めるかを決めます。Agent の提案を機械的に却下した記録もすべて Slack に残るので、後から「なぜ Agent はあのとき何もしなかったのか」が追えるようになっています。Agent と人間の意思決定の境界をログとして可視化することが、Agent を信頼するための最低条件だと感じます。
悪手を 40 時間放置しない — 準リアルタイム指標での自動ロールバック
この構成を回し始めて 2 か月ほど経った頃、設計の穴に気づきました。深夜 2 時に適用した変更が Analytics 側で初めて丸一日ぶんの数字として読めるのは翌朝、BigQuery エクスポートが揃う 9 時前後です。そしてループ自身がその数字を評価に使えるのは、さらに次の深夜、つまり適用から約 48 時間後になります。
つまり、Agent がガードレールを全て通過した「悪くない見た目の悪手」を出した場合、それは 10% のユーザに丸二晩張り付いたまま放置されます。crash-free users のゲートは起動時に一度見るだけなので、クラッシュを伴わない体験劣化はここをすり抜けます。実際、App Open の最小間隔を広げた提案が適用された翌々日に、experiment バケットだけセッション長が短くなっていることに気づいたことがありました。
対策として入れたのが、遅い指標とは別系統の「速い代理指標」で監視し、閾値を割ったら人を待たずに戻す仕組みです。
代理指標に何を選ぶか
Day 1 リテンションは速く読めません。代わりに選んだのは 広告表示から 3 秒以内のアプリ離脱率 (社内では rage-quit rate と呼んでいます)です。広告を見せられた直後に嫌気がさして落とす行動は、リテンション劣化の先行指標としてよく相関しました。過去 6 か月ぶんを突き合わせたところ、この指標が +1.5pt 以上動いた日の翌 Day 1 リテンションは、平均で 0.9pt 低下していました。
計測はクライアントから Analytics に投げるのではなく、15 分バッチで Firestore のロールカウンタに積む形にしています。Analytics 経由だと結局エクスポート待ちになるためです。
指標 読めるまでの遅延 ロールバック判断に使えるか
Day 1 リテンション(BigQuery) 約 31 時間 不可
eCPM(AdMob API) 約 12 時間 不可
crash-free users(Crashlytics) 約 1 時間 クラッシュ限定で可
rage-quit rate(Firestore カウンタ) 約 8 分 可
差が誤差かどうかを、戻す前に判定する
速い指標には別の危険があります。15 分窓のサンプル数は少なく、素朴に「+1.5pt 超えたら戻す」とすると、単なるゆらぎで頻繁に巻き戻ります。最初の実装ではこれをやってしまい、1 日に 4 回も無意味なロールバックが走りました。
いまは二標本比率の差の 95% 信頼区間を計算し、下限が 0 を上回った場合にだけ発火させています。
// functions/src/rollback/significance.ts
/** 標準正規分布の 97.5 パーセンタイル(両側 95%) */
const Z_95 = 1.959964 ;
export type BucketCount = { events : number ; quits : number };
/**
* experiment バケットの rage-quit rate が fixed バケットより
* 有意に高いか(差の 95% 信頼区間の下限 > 0 か)を判定する。
*/
export function isSignificantlyWorse (
experiment : BucketCount ,
fixed : BucketCount ,
minSamplePerBucket = 800 ,
) : { fire : boolean ; diffPt : number ; ciLowPt : number ; reason : string } {
if (experiment.events < minSamplePerBucket || fixed.events < minSamplePerBucket) {
return { fire: false , diffPt: 0 , ciLowPt: 0 , reason: "sample too small" };
}
const pE = experiment.quits / experiment.events;
const pF = fixed.quits / fixed.events;
const diff = pE - pF;
// 差の標準誤差(プールしない版:片側の悪化検出に使うため)
const se = Math. sqrt (
(pE * ( 1 - pE)) / experiment.events + (pF * ( 1 - pF)) / fixed.events,
);
const ciLow = diff - Z_95 * se;
return {
fire: ciLow > 0 ,
diffPt: diff * 100 ,
ciLowPt: ciLow * 100 ,
reason: ciLow > 0 ? "experiment significantly worse" : "within noise" ,
};
}
発火条件はさらに「連続する 2 窓で fire === true」としています。1 窓だけの有意判定は、Play ストアのフィーチャー掲載などでユーザ構成が急に変わったときに誤爆するためです。実際に 6 か月で 1 回、この経路の偽陽性が出ました。
この検定を、記事の後半で触れる eCPM の実績値にも当ててみると話が引き締まります。experiment バケットの eCPM が fixed バケットに対して平均 +3.2% という数字は、6 か月ぶんのユーザあたり日次収益で 95% 信頼区間を取ると −0.4%〜+6.9% でした。プラス側に寄ってはいるものの、ゼロを跨いでいます。自分の仕組みを褒めたくなる数字ほど、同じ検定にかけておくべきだと思います。
キー単位で戻す — remoteConfig:rollback を安易に使わない
ロールバックの実装で最も引っかかったのがここです。Firebase Remote Config には projects/{project}/remoteConfig:rollback というエンドポイントがあり、versionNumber を渡すだけでその版に戻せます。手軽なのですが、戻るのはテンプレート全体 です。Agent の変更のあとに私が手で別のキーを直していた場合、その修正もまとめて消えます。
そのため、通常経路ではテンプレートを取得して対象キーだけを書き戻し、ETag の If-Match で楽観ロックをかける方式にしています。テンプレート全体の :rollback は、キー単位の書き戻しが 3 回連続で競合したときの最終手段としてのみ呼びます。
// functions/src/rollback/revert-key.ts
import { google } from "googleapis" ;
const RC_BASE = "https://firebaseremoteconfig.googleapis.com/v1" ;
export async function revertKey (
projectId : string ,
key : string ,
previousValue : string ,
) : Promise <{ reverted : boolean ; note : string }> {
const auth = new google.auth. GoogleAuth ({
scopes: [ "https://www.googleapis.com/auth/firebase.remoteconfig" ],
});
const client = await auth. getClient ();
const token = ( await client. getAccessToken ()).token;
// 1. 現行テンプレートと ETag を取得
const getRes = await fetch ( `${ RC_BASE }/projects/${ projectId }/remoteConfig` , {
headers: { Authorization: `Bearer ${ token }` },
});
const etag = getRes.headers. get ( "etag" );
const template = await getRes. json ();
if ( ! etag) {
return { reverted: false , note: "etag missing; abort" };
}
if ( ! template.parameters?.[key]) {
return { reverted: false , note: `key disappeared: ${ key }` };
}
// 2. 対象キーだけを前の値に書き戻す(他のキーには触れない)
template.parameters[key].defaultValue = { value: previousValue };
// 3. If-Match で楽観ロック。競合したら 409 が返る
const putRes = await fetch ( `${ RC_BASE }/projects/${ projectId }/remoteConfig` , {
method: "PUT" ,
headers: {
Authorization: `Bearer ${ token }` ,
"Content-Type" : "application/json; UTF-8" ,
"If-Match" : etag,
},
body: JSON . stringify (template),
});
if (putRes.status === 409 ) {
return { reverted: false , note: "etag conflict; caller should retry" };
}
if ( ! putRes.ok) {
return { reverted: false , note: `unexpected status ${ putRes . status }` };
}
return { reverted: true , note: `reverted ${ key } -> ${ previousValue }` };
}
書き戻しに使う previousValue は、Agent の提案を適用する apply.ts の側で、適用と同一トランザクションで Firestore に保存しています。「戻し先を決めてから進む」という順序を守らないと、深夜に壊れたときに戻る場所がありません。
半年で 3 回発火した — 内訳と限界
この自動ロールバックは、半年で 3 回動きました。内訳は真陽性 2 件、偽陽性 1 件です。真陽性の 1 件は App Open の最小間隔を広げた提案で、間隔が空いたぶん「久しぶりに全画面広告が出た」という体感になり、離脱が増えたと見ています。適用から復旧までの平均は 41 分でした。人間が翌々日の朝に気づいて戻していた頃と比べると、露出時間はおよそ 70 分の 1 です。
限界も書いておきます。この仕組みが拾えるのは、広告直後に即座に現れる粗い劣化だけです。「使い続けるのが少しずつ億劫になる」種類の劣化は代理指標に出ないので、結局は Day 1 リテンションの遅い監視と併走させる必要があります。速い指標は事故を止めるためのもので、良し悪しを決めるのは今も遅い指標のほうです。
6 か月運用しての所感
この仕組みを 6 か月運用した結果、手応えのある変化と、手応えのない変化の両方がありました。手応えのあった変化を先に挙げます。
広告監視に費やす時間が 1 日 30〜45 分 → 1 日 5〜10 分 に減った
過去 6 か月で広告関連の事故(Day 1 リテンション 1.5pt 超下落)は 0 件
experiment バケットの eCPM が、fixed バケットに対して平均 +3.2% で推移(ただし 95% 信頼区間は −0.4%〜+6.9%)
自動ロールバックの発火は 6 か月で 3 回 (真陽性 2・偽陽性 1)、平均復旧時間 41 分
手応えがなかったほうの話もしておきます。「Agent に任せたら eCPM がぐっと伸びる」という期待は、現時点では裏切られています。experiment バケットの +3.2% は、fixed バケットを保守的に運用しているぶんが大きく、Agent 単独の貢献は誤差の範囲です。Agent の現在の役割は「私の判断を肩代わりする」よりも「私が手を動かさなくても収益のボラティリティを抑える」ことのほうが近いと感じています。
それでも、毎日の監視から解放された 30 分は、確実に別の作業へ回っています。収益の仕組みは、派手に伸ばすことよりも、目を離しても壊れないことのほうが結局は長持ちする。半年動かしてみて、その感覚のほうが強くなりました。Agent を組み込んだ Remote Config のループは、いまのところその方向に働いてくれています。
次に試したいこと
最後に、これから試したい改善を 2 つだけ書いておきます。
1 つ目は、experiment バケットを「ユーザのアプリ内行動セグメント」で切り直すことです。現在は単純なランダム 10% ですが、Firebase Analytics のオーディエンスを使えば、「過去 30 日でリワード広告を 3 回以上見たユーザ」「IAP 未購入かつ Day 7 まで継続したユーザ」のような行動セグメントに対して、別々のチューニング戦略を当てられます。すでに 5 つほどのオーディエンスを定義済みで、来月以降に Agent プロファイルを分けて当て込む予定です。
2 つ目は、Antigravity の Background Agent を使って、Agent の判断ログ自体を別の Agent にレビューさせる仕組みです。週次で「先週 Agent が出した提案 7 件のうち、却下された 5 件は妥当だったか」「適用された 2 件は事後的に見ても正解だったか」を別の Agent に評価させ、結果を playbook.md の改訂材料にする、という二段構えです。Agent の判断品質を、別の Agent と過去データで継続的に評価する仕組みは、本番投入の前提条件として、今後どの個人開発者にも必要になっていくと感じています。
仕組みのコード一式とプレイブックの実例は、私が運営するサイトの dolice.design 側で別途まとめる予定です。同じように個人で複数アプリを運用しながら AdMob と Remote Config を回している方の参考になれば幸いです。お読みいただきありがとうございました。