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.

antigravity455agents142image-classificationbatch-pipelineapp-dev54

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 $15 for lifetime access
View Membership →

Related Articles

App Dev2026-09-10
When the iOS 27 Date Landed, the First File I Grepped Was AGENTS.md
iOS 27 and iPadOS 27 ship on September 14. Old version numbers linger in your source, but also in the files only your agent reads. Here is a script that counts both zones, plus the one line keyword search will never find.
App Dev2026-09-04
The Five Days Between the iOS 27 RC and Release — I Check Init Order and Entitlement Restore Before Layout
A record of how I narrow the agent's search scope during the short window before an OS release — by launch path rather than by screen. Includes the audit script I run to catch consent and ads init ordering, entitlement restore gaps, and hardcoded size branches.
App Dev2026-08-24
The Cleanup Step Removed the Working Directory, Not Its Contents — Making Unattended Destruction Fail Closed
An unattended cleanup step deleted the working directory itself instead of what was inside it. Here is why the mkdir -p that followed was not a safety net, and how a defensive-looking default value ended up selecting the destructive branch, with the actual verification output.
📚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