Three Decisions I Kept for Myself When Antigravity Maintained My Older Apps
How far could Antigravity maintain my long-running wallpaper and wellness apps? A field log of crash fixes and a staged rollout, with the lines I drew between delegated and retained work, and how I write context notes.
Last month, crash reports kept arriving for the Android version of one of my wallpaper apps.
Tracing them back, the cause was a list being modified while RecyclerView was updating, which produced an IndexOutOfBoundsException. Over 28 days, more than 50 users had been affected.
I knew the fix. The trouble was that the same structure existed in several screens across several apps, and my energy ran out before the typing did.
So I handed part of the maintenance to Antigravity. Here is what I handed over, what I did not, and what I was thinking at the boundary.
The fix itself could be delegated
I fixed the RecyclerView crash by defensively copying the list at update time. Once the fix was settled in one app, I asked the agent to apply the same diff to similar screens, respecting each app's naming conventions.
// Goal: the adapter must not crash even if the source list changes mid-update// Before: held the caller's list directlyclass WallpaperAdapter(private var items: MutableList<Wallpaper>) : RecyclerView.Adapter<WallpaperHolder>() { fun update(newItems: MutableList<Wallpaper>) { items = newItems // later mutation by the caller can cause out-of-range access notifyDataSetChanged() }}// After: copy on receipt; the adapter only ever reads its own copyclass WallpaperAdapter(items: List<Wallpaper>) : RecyclerView.Adapter<WallpaperHolder>() { private var items: List<Wallpaper> = items.toList() fun update(newItems: List<Wallpaper>) { items = newItems.toList() // why: ownership stays inside the adapter, blocking outside changes notifyDataSetChanged() }}
The agent reported, "One app calls it AdManager, another AdvertisingService, so I adapted each accordingly." Doing this by hand, I would certainly have mixed up a name or two.
This kind of work shares two traits: the same shape appears three or more times, and the result can be measured. Whether crashes dropped is answered by the post-release dashboard.
The one-line fix was the hardest part
There was another, different crash.
After upgrading Glide to 5.0.5 and aligning Android Gradle Plugin to 9.x, only Android 6.0.1 devices crashed with NoClassDefFoundError: the Java 8 Supplier could not be found.
When I asked the agent for the cause, it offered several candidates: pin the library, raise minSdk, or avoid the offending call. Each was plausible, and each had wide side effects.
The actual fix was one line: coreLibraryDesugaringEnabled true, which lets older devices use newer APIs by transforming code at build time.
What I took from it: an agent is good at listing every way to fix something, and weaker at choosing what to leave unchanged. Raising minSdk would have meant giving up users on older devices. That is an operations decision, not a code problem.
I chose the one-line option and recorded the rejected ones in a note, along with why.
✦
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
✦After reading, you can sort the work in your own old repositories into 'safe to hand to an agent' and 'keep in your hands' using three questions
✦After reading, you can write a 3-4 sentence context note that stops an agent from helpfully deleting a workaround
✦After reading, you can decide, before release day, what to watch at each staged-rollout step (5%, 25%, 50%, 100%) and the numbers at which you stop
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.
After a few weeks, the decisions I kept narrowed to three.
First, code that carries a "why" stays off limits. One of my wallpaper apps has a process that looks redundant but must not be removed: a workaround for a past store review rejection.
Ask an agent to tidy it, and it will, naturally. From the outside it looks odd, so the cleanup is well-meant.
Second, numbers that define how the app feels are mine to decide. The fade-in time of a sound effect in a wellness app is technically a one-line change. But that number came from listening over and over. I will accept suggestions. The decision stays with me.
Third, the final draft of anything addressed to users is mine. For release notes on a crash fix, or replies to reviewers who hit a past bug, I let the agent draft and I write the last version.
Writing a context note that protects a workaround
Of the three, the first one paid off most. Each repository now holds a small note of 3-4 sentences.
It is not a design document. It follows only this format:
Item
What to write
Example
Where
File and function
Keep the ad-gate check functions as parallel, independent branches
Forbidden
The cleanup you do not want
Do not nest them or merge them into one function
Why
What happened before
Nesting made priority implicit and made faults hard to isolate
Release condition
When touching is allowed
Only when I am present for an ad SDK replacement
Since adding these notes, the agent has almost never tried to tidy a workaround. Writing "do not touch" is not enough: attach the reason and the release condition. A note without a reason seems easy for an agent to skip as an outdated instruction.
Decide the stop criteria before a staged rollout
I shipped the Android update as a staged rollout: 5%, 25%, 50%, 100%.
This is the part I did not hand over.
At each step I watch two numbers: Crash-free users at or above 99.7%, and ANR below 0.20%. If either is missed, I stop and do not advance.
I decide these thresholds before release. If I tell an agent "proceed if it looks fine," the definition of "fine" stays vague. Measurable work can be delegated, but the threshold at which to stop is something a human sets first.
Stop criteria decided only in words tend to wobble on release day. So I locked them into one script: you pass in the numbers you read off Play Console, and it answers only whether you may advance.
#!/usr/bin/env python3"""rollout_gate.py — decide whether a staged rollout may advanceGoal: never advance a stage on a feeling of "looks fine"Usage: python3 rollout_gate.py --stage 25 --crash-free 99.82 --anr 0.14"""import argparseimport sys# Why per-stage: numbers at the small 5% stage are noisy, and I want to be# strictest just before 100%. Thresholds are set by me before release and fixed here.GATES = { 5: {"crash_free_min": 99.5, "anr_max": 0.30}, 25: {"crash_free_min": 99.7, "anr_max": 0.20}, 50: {"crash_free_min": 99.7, "anr_max": 0.20},}def main() -> int: p = argparse.ArgumentParser() p.add_argument("--stage", type=int, required=True, choices=sorted(GATES)) p.add_argument("--crash-free", type=float, required=True) p.add_argument("--anr", type=float, required=True) a = p.parse_args() g = GATES[a.stage] problems = [] if a.crash_free < g["crash_free_min"]: problems.append(f"Crash-free users {a.crash_free}% < {g['crash_free_min']}%") if a.anr >= g["anr_max"]: problems.append(f"ANR {a.anr}% >= {g['anr_max']}%") if problems: print("STOP: " + " / ".join(problems)) return 1 # non-zero exit blocks whatever command would advance the stage print(f"GO: stage {a.stage}% is within limits. Safe to advance.") return 0if __name__ == "__main__": sys.exit(main())
Running it looks like this:
$ python3 rollout_gate.py --stage 25 --crash-free 99.82 --anr 0.14GO: stage 25% is within limits. Safe to advance.$ python3 rollout_gate.py --stage 25 --crash-free 99.61 --anr 0.14STOP: Crash-free users 99.61% < 99.7%
I do not let the agent edit these thresholds. Running the script and reading the result is fine to delegate, but only I may change the numbers in GATES. The days I want to loosen a limit are usually the days I am in a hurry to ship.
One more issue came up: three dialogs (a purchase prompt, a review request, and a rewarded-ad invitation) could appear at the same instant. I fixed it by centralizing modal management in one place. Here too, I wrote down the conflict rules for the three dialogs in words first, and only then let the agent implement them.
Three questions for sorting work
Before handing something over, I now ask myself three questions.
One: does the same shape of work appear three or more times? Two: can the result be measured, with a stop threshold I set first? Three: is this somewhere that touches users' feelings or sensations?
If the first two are yes and the third is no, I hand it over. Otherwise I ask for a draft and keep the final judgment.
Start with one app: pick one task you have repeated three or more times. And next to any code you do not want touched, leave a 3-4 sentence note with a reason and a release condition. The peace of mind after handing work over is quite different.
I am still in the middle of this. Bringing a new tool into old code teaches me something each time.
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.