Handing Nightly Wallpaper Asset Updates to a Background Agent — Time Budgets, Duplicate Detection, and a Completion Gate
A reproducible account of handing nightly wallpaper asset updates to Antigravity's Background Agent. Covers how to write the task definition, perceptual-hash duplicate detection, one-pass resizing across 12 device profiles, a morning report you can review in five minutes, and the time-budget and completion-gate design that makes unattended runs safe.
After the May holidays, I sat down at my Mac and found the Antigravity window quietly open. The Background Agent I had set up the night before had lined up twelve fresh wallpaper assets in a folder, with A/B thumbnails, metadata drafts, and store-description diffs all staged and waiting for my commit. It genuinely felt like something had been tidied by hand overnight, and that morning I found myself looking at the screen before I even made coffee.
I have run iOS and Android apps as an indie developer for many years, mostly in the wallpaper, calm, and well-being genres. The heaviest part of keeping them alive was never the code — it was the small daily work of asset updates and submission chores. This article turns three weeks of handing that nightly routine to Antigravity's Background Agent into steps and code I can reproduce later. Beyond impressions, it leaves the actual shape I run today: task definition, duplicate detection, resizing, and a completion gate.
Why "nightly asset updates" were the first thing to automate
My update rhythm for the parallel iOS and Android wallpaper apps settled into a pattern: sketch new motifs and work on color during the day, export images and check naming conventions at night, then tidy metadata and push to the stores the next morning. The problem was that the nightly export and naming check happened almost every day, and it was simple work with almost no room for judgment — yet terrifying to get wrong.
"Simple but scary to get wrong" is exactly the kind of work that fits an agent well. There is little judgment involved, but when a human does it, attention erodes and a mistake slips in somewhere. With AdMob revenue stable, missing one resolution mismatch or naming slip meant dropping a delivery slot for the night. I rarely had the energy to redo it by hand at midnight, so I had long wanted to let it go.
When I choose what to automate, I measure against three conditions: failure is not fatal, it happens daily, and the judgment space is narrow. Code refactors have too wide a judgment space; final wording of store copy is too fatal to get wrong. What remained was the "night watch" over images, text, and store files — a well-sized first step.
The pipeline that settled after three weeks
The configuration that settled after three weeks ended up far plainer than my first design. I give the Background Agent a time window and have it process exactly four things in order:
Validate resolution, color space, and naming conventions for the 10–20 new assets exported the previous day
One-pass resize and re-export across iPhone / iPad / Android tablet resolutions (12 profiles)
Generate metadata drafts for App Store / Google Play in 9 languages (ja, en, zh-Hant, ko, de, fr, es, pt-BR, it)
Write a single Markdown diff report by 5:00 a.m. and place it at the top of the repository
At first I greedily designed automatic rollback on failure, but I removed it on day three. The more complex the delegation boundary, the longer my morning review took, and the human load went up rather than down. Today it runs on a simpler principle: if something fails, stop the work and defer to me in the morning. In a craftsman's terms — when in doubt, set the tool down.
Subtracting scope worked better than adding tools. Early automation is more stable when you design by subtraction.
✦
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
✦How to write a Background Agent task definition with a time budget (start 22:00, finish by 05:00) for unattended nightly runs
✦Implementation code for perceptual-hash (pHash) duplicate detection and one-pass resizing across 12 device profiles
✦A Nightly report design that keeps the morning review under five minutes, plus the line between what to delegate and what to keep
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.
For unattended runs, the first thing that helped was constraining the task with a single concept: a time budget. Rather than writing timeout, scope, and completion conditions separately, I give them as one frame. Here is the definition I actually use.
# .antigravity/tasks/nightly-asset-update.yamltask: nightly-asset-updateschedule: "0 22 * * *" # start 22:00 JSTbudget: wall_clock: "7h" # must finish by 05:00 hard_stop: "04:50" # past this, interrupt mid-task and reportscope: source: "assets/wallpapers/incoming" lookback_days: 30 # dedup only scans the last 30 dayssteps: - validate_assets - resize_profiles - draft_metadata - write_reporton_uncertain: halt # halt when unsure (no auto rollback)report: "_logs/nightly/{{date}}.md"
I keep wall_clock and hard_stop separate so that even if the start time drifts, the invariant holds: by 5:00 a.m. there is something I can look at. If the task overruns, it writes a report at whatever stage it reached and stops at 04:50. An unfinished report waiting in the morning is far easier to deal with than no report at all.
on_uncertain: halt is reinforced in the agent's system prompt as well: "do not run anything you are unsure about — stack it under Needs Your Review and move on." With that, the night does not spread an incident, and I can adjudicate everything at once in the morning.
Perceptual-hash duplicate detection
The scariest accident for a wallpaper app is shipping the same image twice under different filenames. When you reassign sequence numbers and category prefixes by hand, this happens more than you would expect. I have the Background Agent catch it with a perceptual hash (pHash).
# scripts/detect_duplicates.pyfrom pathlib import Pathfrom datetime import datetime, timedeltafrom PIL import Imageimport imagehashLOOKBACK_DAYS = 30THRESHOLD = 5 # Hamming distance; <= 5 is treated as "near-identical"def recent_assets(root: Path, days: int): cutoff = datetime.now() - timedelta(days=days) for p in root.rglob("*.png"): if datetime.fromtimestamp(p.stat().st_mtime) >= cutoff: yield pdef scan(root: Path): hashes = {} flagged = [] for path in recent_assets(root, LOOKBACK_DAYS): h = imagehash.phash(Image.open(path)) for known_path, known_h in hashes.items(): distance = h - known_h if distance <= THRESHOLD: flagged.append((path, known_path, distance)) hashes[path] = h return flaggedif __name__ == "__main__": for dup, original, dist in scan(Path("assets/wallpapers")): print(f"NEAR-DUP: {dup.name} ~ {original.name} (distance={dist})")
LOOKBACK_DAYS = 30 is there for a reason. In the first week I had it scan every asset ever generated. Task runtime grew daily — four hours on day three, six on day five, over eight by day seven — until the morning processing no longer finished in time. The moment I narrowed scope to the last 30 days, it settled to about two hours. Long tasks are stable when you bound them by time, not by count.
The threshold of 5 is also a field-tuned value. At 0 it only catches exact matches and misses effectively identical images that differ by a slight export setting. Pushed to 10, it surfaces color variants of the same series as "duplicate candidates," filling the morning review with noise. Tuning this one line to your own asset tendencies is the heart of the operation.
One-pass resize across 12 device profiles
Resizing is the textbook case of work that should produce the same result no matter who does it — exactly where the Background Agent's meticulousness shines. I gather target resolutions per device into a single dictionary and normalize to sRGB before export.
The script itself is plain, but the value of delegating it is the repetition: 12 profiles × 10–20 assets per day ≈ 200 exports, kept naming-consistent every single night. A human will inevitably skip a sequence number or mislabel a profile somewhere in 200 passes. After three weeks I am convinced this is a region you can delegate without hesitation.
What to delegate and what to keep
After three weeks, the line became clear in my own mind. The axis is simple: in the experience a user receives, is this a part where the human touch should remain? The choice of which pieces to ship, and the tone of the closing line in the copy, should carry my own warmth. Conversely, filename and resolution consistency should produce the same result for anyone, so the Background Agent finishes them more beautifully than I would.
Task
Decision
Reason
One-pass resize across 12 profiles
Delegate
Result is unique; humans err on repetition
Filename sanitization / sequence consistency
Delegate
Rules are clear; meticulousness is the value
Perceptual-hash duplicate detection
Delegate
The machine is more thorough and accurate
Color-space check (P3 / sRGB mixing)
Delegate
Inspection code beats the eye
Multilingual metadata drafts
Draft only
Final tone check is always human
Choosing which pieces go in today's slot
Keep
The first judgment carries the work's warmth
Final wording of store copy
Keep
The tone of the closing line cannot be let go
Sending review replies
Keep
AI drafts; the human checks before sending
This table firmed up after going back and forth several times over three weeks. At first I even wanted to eyeball the duplicate detection myself, but once I saw the machine was more accurate, I could let it go. The metadata tone, on the other hand, is something I want to touch last no matter how many times I delegate it. That very feeling is, I think, an important signal for keeping the outline of the work intact.
A Nightly report that keeps the morning review under five minutes
The single most important thing for sustaining nightly automation is not increasing my load when I check in the morning. The first week took over 30 minutes each morning because the agent's output log was long and what to verify was scattered. By the third week, the review flow collapsed into one Markdown report.
# Nightly Report 2026-05-22## Status- Total assets processed: 18- Resize variants generated: 216 (18 × 12 device profiles)- Metadata drafts: 9 languages × 18 = 162 files- Duplicates flagged: 1 (needs human review)- Color space anomalies: 0## Needs Your Review1. assets/wallpapers/2026-05/aurora-014.png - Detected as near-duplicate of aurora-009 (perceptual hash distance: 4) - Action requested: Keep both? Replace? Skip?## All Clear- aurora-001 ... aurora-013, aurora-015 ... aurora-018: processed as specified- Metadata drafts: all passed tone check (final approval by human)## Time- Started: 22:03- Completed: 04:48- Total: 6h 45m
The point is to put only the things that need human judgment at the top. List everything in parallel and your eyes slide off, and you end up re-reviewing all of it. I have the Background Agent report confident results separately from uncertain judgments, hammered into the system prompt. With this single report, the morning check fits inside three to five minutes. Even while traveling, opening it on an iPad and replying "skip that image" is enough to keep the next day's delivery rolling — a feeling that has been special even across years of running these apps.
Three pitfalls I hit in three weeks
Real operation is not all pretty stories; some nights did not go well. I hope these help anyone running apps at a similar scale.
1. Task runtime keeps growing. As above, full-history scanning pushed runtime past eight hours. I solved it by narrowing scope to the last 30 days and writing lookback_days explicitly into the task definition. Long tasks should be bounded by time.
2. Tone drift in metadata drafts. When I first delegated multilingual drafts, the warmth of the Japanese closing lines drifted day to day. "Please try it" and "I'd be glad if you tried it" mixed together, quietly lengthening my morning edits. After adding "reference the last 30 release notes and keep the closing tone consistent" to the prompt, they mostly aligned. Giving the prompt a concrete reference source for tone is what works.
3. Mac sleep versus the Background Agent. When the Mac entered full sleep, the agent's session sometimes stopped with it. In my setup I run caffeinate -i -t 28800 only at night, letting the display sleep while keeping the CPU awake. On Apple Silicon efficiency cores, the power draw was negligible.
# Suppress sleep only at night (8 hours starting 22:00)caffeinate -i -t 28800 &
The completion gate — before you believe "done"
Finally, the mechanism that helped most in three weeks: even when the Background Agent reports "processing complete," do not trust the report alone. Always insert one step just before completion that inspects the artifacts themselves. Ahead of report generation, I have it mechanically verify that the exported file count matches the profile count.
# scripts/completion_gate.pyfrom pathlib import Pathimport sysEXPECTED_PROFILES = 12def verify(out_root: Path, source_count: int) -> bool: expected = source_count * EXPECTED_PROFILES actual = sum(1 for _ in out_root.rglob("*.png")) if actual != expected: print(f"GATE FAILED: expected {expected} files, found {actual}") return False print(f"GATE OK: {actual} files") return Trueif __name__ == "__main__": ok = verify(Path("assets/output"), int(sys.argv[1])) sys.exit(0 if ok else 1)
When this gate fails, the Background Agent writes the report as "unfinished" and waits for my judgment. It guards against the worst pattern — "reported as successful but actually missing." To sleep soundly under unattended runs, the conclusion is to trust an inspection that counts artifacts, not the agent's self-report.
Over the next month I plan to run two more Background Agent tasks in parallel: improving the quality of review-reply drafts, and proposing AdMob delivery-slot optimizations. By handing off more of the manual work, I can concentrate a little more on conceiving new pieces. I hope this helps anyone who, like me, has run apps solo for a long time lighten the repetitive work of the night. Thank you 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.