The trigger was a small, embarrassingly ordinary mistake. One of my wallpaper apps, six months past its last big release, had been running with an interstitial floor lower than I intended, and the month-over-month eCPM was down by roughly 12%. When I finally opened the AdMob dashboard, the floor was sitting at the value I had temporarily lowered for a test, and on the Remote Config side interstitial_enabled had been switched to false, so for a stretch of time the unit was barely serving at all. Two surfaces, two settings, and a forgotten "undo" on one of them. A classic drift.
I have been running an app business as a solo developer since 2013, and the catalog has crossed roughly 50 million downloads over the years. Ad settings naturally get nudged release after release, so the central question for keeping ad revenue healthy is not "how do I optimize?" but "how do I notice when something has quietly diverged?" I had just begun pointing an Antigravity Background Agent at release monitoring, and decided to extend it with one more small job: a weekly diff between AdMob and Remote Config. The result of six weeks of that experiment is what this article is about.
Why I scoped this as "a job no human will keep doing"
Both of my grandfathers were temple carpenters, and from them I inherited a quiet conviction that anything you build carefully also deserves to be maintained carefully. Right after a release, energy is high and I open the AdMob dashboard every day. Three months later, six months later, that frequency naturally drops because the next product needs my attention. The moment it drops is exactly when drift creeps in.
I deliberately kept the agent's job small at first:
- Snapshot the configuration of each AdMob ad unit
- Snapshot the ad-related Firebase Remote Config keys (
*_enabled,*_freq_cap,*_floor, etc.) - Diff today's snapshot against last week's, side by side
- Surface only the items where one side moved and the corresponding key on the other side did not
What I did not include: "propose an improvement" or "automatically write the value back." Writes stay on the human side. That was the foundational rule.
The shape of the actual job
After a couple of iterations the agent definition settled into roughly this shape — trimmed for readability but very close to what runs:
# antigravity/agents/admob-rc-weekly-diff.yaml
name: admob-rc-weekly-diff
schedule: "0 3 * * MON" # 03:00 JST every Monday
tools:
- admob.list_ad_units
- admob.get_ad_unit
- firebase.remote_config_template
- storage.kv # storage for last week's snapshot
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])The load-bearing piece is pair_rules ($AD_UNIT_RC_MAPPING). Rather than trusting a naming convention to keep ad unit names and Remote Config keys aligned, I keep an explicit pairing table — about 80 lines of YAML. Naming conventions always drift in the long run, and the moment they do, the agent loses confidence in its own findings. An explicit map makes "only one side moved" a statement the agent can make without flinching.
What it actually caught in six weeks
The catches were unglamorous, which was exactly the point:
- Week 1: I lowered
interstitial_freq_capin Remote Config from 5 to 3, but forgot to restore the AdMob floor I had temporarily reduced. eCPM had been quietly depressed for days. - Week 3: I created a new
rewarded_v2ad unit on the AdMob side, but never addedrewarded_v2_enabledto Remote Config. In production the unit was never served at all. - Week 5: An A/B test in Remote Config moved from 50/50 to 70/30, but one of the variant arms still pointed at a stale mediation group on the AdMob side.
None of these are detective work. They are "you would notice if you looked." What the agent gave me was a fixed time to look — Monday morning, while the coffee is brewing — instead of relying on memory.
Where I hit walls
It was not all smooth.
The first wall was granularity. AdMob's mediation groups carry a lot of per-network settings, and snapshotting all of them produced a JSON blob north of 800 KB. The diff became unreadable. For the first two weeks I narrowed the scope to "unit-level enable/disable, floor price, and frequency cap" only, and excluded mediation internals. What deserves to be classified as "only one side moved" is something you can only figure out after you run the job for a while.
The second wall was the boundary of authority. I initially imagined giving the agent write access to Remote Config so it could repair drift autonomously. I tried this in a sandbox, and on the first run it nudged a variant ratio in the middle of an active A/B test. That experiment ended right there, and "writes are always human" became a permanent rule. Part of the value of a Background Agent, it turns out, is that it makes visible the territory where not writing is the most valuable thing it can do.
The third wall was notification noise. When the agent sent the same "no changes this week" report every Monday, by the third week I had stopped reading it. Now the report is a single quiet line when the diff is empty, and a full breakdown only when there is something to look at.
The line I ended up drawing
After six weeks, the shape of "agent-suitable work" in my head looks like this:
- Suitable: cross-surface configuration consistency checks, 48–72h post-release crash rate monitoring, new review surfacing, store console compliance alerts.
- Not suitable: floor price optimization decisions, deciding AB test winners on UI changes, automatic mediation order rewrites.
What the suitable items share is that the value is fully captured at "find the diff." How to act on it remains a human decision that needs business-phase context the agent simply does not hold.
This mirrors something a mentor told me when I was seventeen, learning to publish things online for the first time: "Art should be a natural language open to everyone." The technical translation, for me, is that when the language of the work is open enough to share, the work can be delegated. When the judgment depends on context the tool cannot see, the judgment stays with the author. That distinction is doing more work in my practice now than any single technique.
What I want to try next
For the next six weeks I plan to expand pair_rules from "one unit, one key" to "one unit, one set" (for example, rewarded_v2 ⇄ rewarded_v2_enabled + rewarded_v2_floor + rewarded_v2_freq_cap) and evaluate consistency across the whole set rather than pair by pair.
The Background Agent is quietly becoming a kind of second self for the repetitive, unflashy work of keeping things in shape. If you are a solo developer trying to keep an ads-backed app healthy without losing your evenings to dashboards, I hope a corner of this helps. Thank you for reading.