Tuning AdMob Placements at Runtime with Firebase Remote Config and an Antigravity Agent
A practical look at how I combine Firebase Remote Config with an Antigravity Agent to nudge AdMob placements while the app is live, drawn from years of running wallpaper apps as an indie developer.
Of all the genres I have shipped since I started building apps on my own back in 2014, wallpaper apps are the one I have lived with the longest. Somewhere after the cumulative install count crossed 50 million, my weekly time budget shifted away from new features and toward a single quiet question: how do I keep the ad load and the user experience in balance? Each release I would inch the AdMob dashboard settings for App Open, interstitial, and rewarded placements. Each release something somewhere would slip — Day 1 retention, eCPM, IAP conversion. Even when I was paid by the apps, that loop felt like it was paying with my weekends.
For the past few months I have been pulling that work apart and reassembling it as a small, safe nightly tuning loop built around Antigravity's Agent and Firebase Remote Config. The parts that work give me real confidence, and the boundaries where I have decided not to let the Agent in have become much clearer. What follows is the whole picture, with the operational details I have only learned by running it in production on apps that earn real money.
The catalyst: a 2% Day 1 dip I could not explain
The trigger was last fall's release. I changed App Open frequency from "once per session" to "suppress on Day 0, unlimited from Day 1 onward." Within a week, Day 1 retention in Firebase Analytics dropped from 33% to 31%. At my scale of a few hundred thousand DAU, two points matter.
Metric
Before (7-day avg)
After (7-day avg)
Delta
Day 0 → Day 1 retention
33.4%
31.2%
-2.2pt
Interstitial eCPM
$4.12
$4.05
-1.7%
App Open eCPM
$2.85
$3.91
+37.2%
Daily ad revenue
$1,820
$1,910
+4.9%
Short-term revenue went up. The catch was that the retention dip ate into LTV, and three months later the ad revenue had reversed and was sliding. I had bundled three other changes into that release, so I could not tell which knob had moved the needle. I ended up rolling the whole app back to the previous version and rolling back the ad units in the AdMob dashboard by hand. The three days I spent waiting on App Store and Google Play review cost an estimated $5,000 in lost ad revenue.
Sitting on that loss as a single-person operation, the lesson burned in: monetization decisions need to live on a different cadence than your app binary. The moment you couple them to a release, your rollback latency is bounded by store review. That is when I started taking Firebase Remote Config seriously.
Why Remote Config alone was not enough
Remote Config itself slotted in quickly. I moved the variables I wanted to nudge — ad unit IDs, frequency caps, the Day 0 suppression flag, the rewarded multiplier — into Remote Config, and the app reads them at launch via fetchAndActivate() and passes them into the AdMob wrapper. Tweaks no longer needed a release, and rollback was just "set it back to the previous value."
What Remote Config did not solve was the harder problem: deciding which value to move next. That part stayed manual. Each morning looked like:
Look at eCPM trends in the AdMob dashboard
Look at Day 1 retention in Firebase Console
Check Google Play Console and App Store Connect for crashes and reviews
Synthesize all of the above into a judgment like "today let's pull App Open frequency from 1.0 to 0.7"
Update Remote Config
Thirty to forty-five minutes of that, every morning, quietly eats the rest of my indie dev day. Across multiple apps in parallel, weekends started to feel like an extension of ad monitoring. The question of this piece is whether Antigravity's Agent can be inserted into that loop.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Four naming conventions that make Remote Config and AdMob safe to operate together
✦A concrete nightly tuning loop powered by an Antigravity Agent
✦How to choose the share of users your Agent is allowed to touch without breaking eCPM or Day 1 retention
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The first principle I locked down: do not give the Agent full authority to decide. Remote Config ships directly to live users, so an Agent that goes off the rails can break every user's experience at once. The boundaries I chose are:
The only parameters the Agent can touch are Remote Config keys marked "dynamic-tunable"
Any value change is capped at ±15% of the current value, and must stay within a pre-defined absolute floor and ceiling
The change applies to at most 10% of users (controlled via a Remote Config condition)
The Agent may only use Firebase Analytics, AdMob, and Crashlytics metrics as inputs. Moving values based on a sentiment read of review text is forbidden
I wrote these boundaries into AGENTS.md as guardrails the Agent is required to read. The relevant section looks like this:
## Constraints on automated Remote Config tuningIn this repo, only Remote Config keys with the `tunable.` prefix are eligiblefor Agent-driven adjustment. Keys with the `fixed.` prefix must never bemodified by the Agent.- At most 2 keys may be changed in a single tuning pass- New values must stay within ±15% of the current value- Changes apply only to the "audience: opt_in_experiment" segment (10% of users)- Each key may be changed at most once per day- If the last 24h Crashlytics crash-free users metric drops below 99.0%, no automated tuning runs at all (human review required)
Only after this was written down could I hand the Agent the "look at the data and decide" role. The next step was to express the decision loop as an Antigravity Workflow.
The shape of the nightly tuning loop
The loop runs once a day at 02:00 JST and goes through six steps. I picked the dead of night so that the impact of any change is visible by the time I wake up.
Pull the last 7 days of metrics from Firebase Analytics (BigQuery export) and AdMob
Pull the Crashlytics crash-free users figure. If it is below 99.0%, exit
Pull the last 24 hours of Remote Config change history (Firebase Remote Config REST API)
Hand all of that to an Antigravity Agent and ask for at most two proposed key/value changes
Validate each proposal against the AGENTS.md guardrails. Discard anything that fails
Apply surviving proposals to Remote Config and notify Slack
The first version of this had step 4 flow directly into step 6. A few clearly bad proposals slipped through, so I broke out step 5 as an independent validation step. Having a deterministic, hand-written validator sit between the Agent's proposal and production has held up well.
I do not let the Agent write this validator. The judgment can be delegated, but the validation of that judgment runs through deterministic code I wrote by hand. Once I split those roles, my comfort level running this against production went up another notch.
A gotcha around metric freshness
Anyone running the BigQuery export for Firebase Analytics knows the default: previous-day data lands a few hours into the morning. If your tuning loop runs at 02:00, what it actually has access to is the day before yesterday's data. If you don't recognize this, the Agent will propose changes based on a world that doesn't reflect what you just did.
I always prepend the Agent's context with a freshness note:
Data freshness notes:- Firebase Analytics: last 24h not included (BigQuery export spec)- AdMob API: today's figures finalize in the afternoon, so the last 12h are excluded- Crashlytics: roughly real-time (about 1 hour delay)So at 02:00 today, the most recent Analytics data available is from 5/19.The effect of any Remote Config change you made yesterday (5/20) cannot yet bejudged. If a change exists from yesterday, mark it as "impact unmeasured" andhold off on new proposals.
Since adding this note, the Agent has stopped its most dangerous behavior — changing a value immediately after a previous change. Separating "what to prevent with prompts" from "what to prevent with code" is a discipline you have to develop deliberately when you put Agents in production.
Wrapping it in an Antigravity Workflow
Antigravity's Workflow feature ties the whole loop together in a single file. Mine lives at .antigravity/workflows/admob-nightly.yaml:
agent.profile references an Agent profile registered on the Antigravity side. The profile definition constrains the tools the Agent may use, the AGENTS.md sections it may read, and the output format it has to produce.
Forcing output_schema means I always get a structured JSON alongside the Agent's natural-language reasoning. That JSON is what flows into the validator. If you let an Agent emit free-form text downstream, format drift is guaranteed somewhere. The biggest lesson from running this for six months is to enforce a structured handoff at every Workflow boundary.
How to choose the share of users you tune
Setup talk aside, the hardest operational question is: how large a slice of users do you let the Agent touch? My current setting:
90% of the audience runs on hand-set values (the "fixed" bucket)
The remaining 10% is eligible for Agent-driven tuning (the "experiment" bucket)
Inside the experiment bucket, a Remote Config condition further splits it into two sub-buckets: one keeps the 7-day baseline, the other applies Agent proposals
I started at 30% for the experiment bucket and stepped it down. At 30%, when the Agent moved a couple of values in close succession, overall eCPM would swing 5-8%. At 10%, the swing stays under 1% and the worst-case blast radius is something I can reason about.
For an indie developer running on AdMob, revenue predictability is everything. Heavy month-to-month eCPM volatility makes monthly set-asides and forward planning harder. My north star is "monthly revenue volatility under 5%," and the experiment bucket size is chosen to stay under that ceiling.
The judgment layer behind the tuning
The playbook (playbook.md) handed to the Agent spells out the order of judgment in prose:
## Priority of judgment1. If crash-free users is below 99.0%, propose nothing at all2. If Day 1 retention is more than 1.5pt below the 7-day average, only propose changes in the direction of reducing ad exposure (negative adjustments)3. If Day 1 retention is stable (within +/-0.5pt of the 7-day average) AND eCPM has fallen below its 14-day low, only propose format-switching keys4. If none of the above apply, return "no proposal"## Forbidden proposals- Setting the rewarded multiplier above 1.5x- Setting the App Open frequency above 1.5 per session- Flipping the Day 0 suppression flag to false (Day 0 is sacred for retention)
Making the order of judgment explicit stabilizes the Agent's proposals. One subtle distinction: keep the judgment in the playbook, not in AGENTS.md. AGENTS.md is for "guardrails I never want to relax"; playbook.md is for "this week's strategy" and is expected to change. Separating these two roles makes the improvement cycle much easier to run.
Slack notification: separating judgment from intent
A final design note about Slack. Every proposal the Agent makes, applied or not, lands in Slack so I can scan it in the first five minutes of my morning. The body looks like this:
[AdMob nightly tuning] 2026-05-21 02:14 JSTMetrics evaluated (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 proposals (2)1. tunable.interstitial_frequency_cap: 4 -> 3 (-25.0%) reason: eCPM fell below 14d low; reduce frequency to recover unit price -> rejected: delta 25.0% exceeds 15% cap2. tunable.app_open_min_interval_sec: 30 -> 35 (+16.7%) reason: App Open eCPM trending down; widen interval to preserve unit price -> rejected: delta 16.7% exceeds 15% capChanges applied: 0Note: both proposals were rejected by the guardrail this run.If you see three consecutive days of all-rejections, revisit the playbook.
When that lands, I decide whether to nudge Remote Config by hand or whether to loosen the playbook. Every Agent proposal — including the rejections — is logged in Slack, so later I can go back and understand exactly why the Agent did or did not act. Making the boundary between Agent decisions and human decisions auditable is, for me, the minimum bar for trusting an Agent in production.
Six months in: what worked and what didn't
After six months in production, the wins are clear and the limits are clearer.
Wins:
Time spent on daily ad monitoring dropped from 30-45 minutes/day to 5-10 minutes/day
Zero ad-related incidents (no Day 1 retention drops >1.5pt) in the six-month window
The experiment bucket eCPM has averaged +3.2% over the fixed bucket
The honest part: the fantasy of "let the Agent loose and watch eCPM soar" has not materialized. The +3.2% is largely because the fixed bucket is run conservatively. The Agent's standalone contribution is inside the noise. Today the Agent's real job is less "make smarter decisions than me" and more "absorb the daily monitoring burden so revenue volatility stays bounded."
Even so, the half-hour I get back every day shows up where it matters — time for art work, time to talk with my kids. Both my grandfathers were temple carpenters, and watching them build things that hold up for decades shaped how I think about systems. I want a monetization stack that can be sustained for the long run, not one I have to babysit. The Agent-driven Remote Config loop, so far, is moving things in that direction.
What I want to try next
Two improvements are queued up.
First, slice the experiment bucket by in-app behavior segments rather than by random 10%. Firebase Analytics audiences let me address "users who watched rewarded at least 3 times in the last 30 days" or "non-IAP users who lasted past Day 7" as separate cohorts, with separate tuning strategies. I already have five audiences defined and plan to wire up matching Agent profiles in the coming month.
Second, use Antigravity Background Agents to have a second Agent review the first Agent's decision log. Weekly: "of the 7 proposals last week, were the 5 rejections appropriate? Were the 2 applied changes still the right call in hindsight?" The conclusions feed back into playbook.md revisions. Continuously evaluating the quality of Agent judgment using another Agent and historical data is, I suspect, going to be a baseline requirement for any indie developer who wants to put Agents into production.
I will be publishing the full code and playbook examples separately on dolice.design. If you are operating multiple apps and tuning AdMob and Remote Config on your own, I hope some piece of this is useful. Thanks for reading.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.