ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-08-15Intermediate

How Far to Narrow an Agent's Choices in a 30-Category Wallpaper Classification Pipeline

Asking an agent to pick one of 30 categories per image means re-running every image the moment a definition changes. Here is the reasoning and the implementation behind switching to closed-vocabulary tags plus a deterministic rule mapping.

antigravity439agents130image-classificationbatch-pipelineapp-dev50

Premium Article

Sorting wallpapers into thirty delivery categories is the kind of work that piles up quietly. You can tell at a glance which bucket an image belongs in, but there are thousands of them. As an indie developer shipping wallpaper apps, I have a steady supply of tasks shaped exactly like this, and handing the looking-at-images part to an agent felt like an obvious fit.

What tripped me up was not accuracy. After running the pipeline for a few weeks, I wanted to split night-time nature scenes out of the existing "Landscape" bucket. That single change put every previously classified image back in scope. The stored output was a list of image IDs and category names, and nothing in that list tells you whether an image was a dark forest or a sunrise over a ridge. The only way forward was to show every image to the model again.

That was the moment the design flaw became obvious. I had been asking the agent to hand me an interpretation.

Why adding one category meant redoing everything

The saved record was a single line per image: 210756 → Landscape. There is no trace of why that verdict was reached, so there is nothing to re-query when the definition of Landscape narrows.

Category definitions always move in production. You split a bucket because the delivery surface needs finer slots; you merge one because nobody taps it. Any structure that requires full re-inference on every definition change gets heavier as the library grows. My batches are capped at 50 images, twice a day, to stay under rate limits — so a full redo across a few thousand images burns weeks of wall clock time for a change that has nothing to do with the images themselves.

The pixels did not change. Only my bookkeeping did. Re-measuring something that has not moved is, in hindsight, plainly wasteful.

Let the agent observe, let a script interpret

The revised design is simple: the agent reports what is in the image and never names a category.

LayerOwnerOutputOn a definition change
ObservationAgent reading the imageArray of tagsNot re-run
InterpretationDeterministic scriptCategory membershipSwap the rule table and re-run

Observations are facts about the image, so they can be captured once and reused. Interpretation is my bookkeeping, so it lives outside the model where it is cheap to change. Since the split, the number of inference calls required by a category change has been zero.

Reproducibility improved as a side effect. When the model chose one of thirty labels, adjacent categories drifted between runs — the same night cityscape came back as "Night View" one day and "Landscape" the next. Neither answer is wrong, which is exactly the problem: the ambiguity lived in my category boundaries and was getting baked into the output. Asking for night, city, skyline produced almost no such drift. The images were never the ambiguous part.

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
You will be able to draw the line in your own project between what an agent should judge and what belongs in a deterministic rule, assuming your categories will change
You will be able to design your way out of re-running inference over every image each time you add or merge a category
You will know how to resume a 50-item batch after a mid-run failure without dropping items or emitting duplicates
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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-07-18
After Compose-First: Choosing Which View Screens to Migrate, Ranked by Churn Instead of Count
Google has declared Android development Compose-first. Here is how I rank View-based screens for migration using git history rather than screen counts, with the scoring script I actually ran and the three places partial migration quietly duplicates state.
App Dev2026-07-13
The Gate That Stops Visual Damage Before You Hand Bulk Image Optimization to an Agent
When an agent bulk re-encoded a few hundred wallpaper assets, a handful came back with dulled color. Size-reduction alone cannot catch that. Here is how to design a gate that stops bad conversions before merge using three axes — SSIM, ΔE, and file size — with a checker that runs on Pillow and scikit-image.
App Dev2026-07-09
Before an Agent's .proto Edit Silently Breaks Binary Compatibility: Gating Wire Compat with buf breaking
When an agent edits your .proto files, the text can look perfect while wire compatibility quietly breaks. Here is how to stop field-number reuse and unsafe type changes with a working gate built from buf breaking and reserved.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →