Anatomy of a real workflow

3 hours messy.
30 minutes ideal.

100 AI-generated static ads, brand-owner review hub, 46 launched live in Meta — built two different ways. Here's what actually happened, and what the prompt should have been.

What we did
Time~3 hours
Spend (OpenAI)$18.78
Image generation runs3 (v1, v2, v3)
Wasted batches100 ads + 100 ads
Sessions2
Failures hitWrong bottle · cropped text · expired token
What it could have been
Time~30 minutes
Spend (OpenAI)$6.30
Image generation runs1
Wasted batchesNone
Sessions1
Failures hitZero

Step-by-step comparison

Every step that actually ran, vs the version of the step that would have run if the brief had been airtight.

What we did

01
Brief + clarifying questions
"Make 100 ads. Use product cutouts as reference. Approve/reject hub. Back in 1 hour."
02
Pulled live Meta ad data
Last 90d, 91 ads, classified 10 winners + 7 promising. Saved patterns to proven-patterns file.
03
Built 5 angles × 20 formats concept matrix
100 unique ad concepts, each one a single-variable hypothesis.
04
Generated 100 ads with text description of the bottle
Used /v1/images/generations (text-only). AI invented its own bottle each time. $6.24 wasted.
05
Rebuilt with cutout reference + center-crop to 4:5
Switched to /v1/images/edits with the real cutout. But the post-crop chopped headlines placed near the edges. Another $6.24 wasted.
06
Rebuilt again — safe-zone prompts, no crop
Native 1024×1536 with explicit instruction to keep all critical content in the central 4:5 safe area. Worked. $6.30 spent.
07
Deployed review hub to Cloudflare Pages
KV-backed approve/reject API, reviewer name gate, deployed to tinyshield-ads.pages.dev/may-ads.
08
Brand owner reviewed → 46 approved
Pulled decisions via API. Confirmed final set with operator.
09
Launched 46 ads — token died at #29
Meta session token I had claimed was "long-lived" was actually short-lived. Expired mid-run. 29 ads created, 17 failed.
10
Refresh token + finish remaining 17
Verified expires_in ≥ 59 days via debug_token before storing. Saved renewal one-liner. Re-ran launcher. 46/46 live.

What it could have been

01
Brief with all constraints front-loaded
Endpoint specified. Cutout path specified. Aspect ratio + safe zone specified. Deploy target specified. Reviewer flow specified. Token check pre-flighted.
02
Pre-flight: verify token is long-lived
debug_token first. If expires_in < 5,000,000s, exchange to long-lived. Then proceed.
03
Pulled live data + built concept matrix
Same as actual — this step always works.
04
Generated 1 sample ad for approval
Used /v1/images/edits + real cutout + safe-zone prompt. Showed operator first. ~$0.06.
05
On approval, generated remaining 99 in parallel
10-way parallel via threadpool. ~10 minutes total. ~$6 spend.
06
Deployed review hub + KV API
Same Cloudflare deploy, but spec'd upfront so no rebuild.
07
Brand owner approves at her own pace
Out-of-band. No back-and-forth.
08
Launch approved set, one clean run
Token already verified long-lived in step 2. No mid-run expiry. 46/46 created.

What I actually wrote

Here's the prompt I sent in. It was good — conversational, scoped, time-boxed. It even named the constraint that ended up being the failure point.

MY ORIGINAL PROMPT
i need you to review tiny shield's ad account and understand the type of content that is performing and isnt. review the headlines, hooks, messaging, angles, etc. then we need to turn the winning concepts into seriously good iterations. seriously outside the box thinking. there are 22 types of ads in the matrix so lets take every winning angle and make 22 statics using gpt image 2. you have the product cutouts for reference images. you know what works. its important that these look different. id like to see a maximum of 100 static ads. i'll be back in one hour to review. please have them in an html hub where i can easily approve and reject.

What went wrong: I told the agent "you have the product cutouts for reference images." It chose to describe the bottle in text instead of passing the cutout to the image API. Same words, two completely different actions. The agent took the easier path. I lost 2 batches and ~$13 finding out.

The instruction was there. The agent's discipline wasn't. The fix isn't to write a longer prompt — it's to make the prompt impossible to misinterpret.

What I should have written

Same conversational tone. Same scope. But the reference-image instruction is now load-bearing, not parenthetical — and the brand-owner step is explicit so the agent builds the right infrastructure on the first pass.

Build me a 100-ad review hub for my TinyShield account.

Pull my top 5 performing angles from the last 90 days and run each one through all 20 CDM formats. Use the bottle cutout as the reference image — don't describe it, pass it to gpt-image-2 directly. Show me 1 sample before generating the rest.

Deploy the hub to Cloudflare with a way for me to approve/reject each one. When I'm done, launch the approved set into my April Copy campaign as one new ad set going to homepage. UTM everything.

Budget: $25.

Four sentences. ~85 words. Still conversational. But every load-bearing constraint is unambiguous.

What makes 60 words enough

Brevity in the prompt requires depth in the system. Every piece of context the long prompt had to spell out is something a well-built operator already has installed. This is the work you do once so every future brief can be short.

01
Brand state file
A persistent JSON per brand: ad account ID, pixel ID, page ID, cutout file path, brand colors, current winners, hero product. "TinyShield" expands to all of that. Lives in brand-states/tinyshield.json.
02
Pre-built format library
The CDM matrix isn't generated each time — it's a 22-format library with a working prompt template per format, plus a sample image. "All 20 CDM formats" pulls the library, no description needed. See the gallery →
03
Skill library
Reusable skills for each verb in the prompt: performance-decoder handles "pull top performers." creative-iteration-engine handles the matrix run. meta-ads-launcher handles the launch. Each skill enforces its own discipline.
04
Credentials, vaulted + verified
Meta token, app credentials, OpenAI key — all in the OS keychain with provenance metadata and renewal one-liners. "My TinyShield account" doesn't need spelling because the token's already vaulted and the path's documented.
05
Deploy infrastructure
Cloudflare Pages project exists. KV namespace exists. Pages Functions API pattern is documented. "Deploy to Cloudflare so Serina can approve" maps to an existing repeatable pattern, not a from-scratch build.
06
Persistent failure rules
Every mistake from a prior run is saved as a feedback memory the agent inherits. explicit-instructions-are-constraints. credentials-persistence. 1→approval→scale gate. The agent doesn't repeat your past failures because it remembers them.

What this cost me to learn

2 wasted batches. ~$13 in API spend I didn't need to spend. About 2 hours of clean-up across two sessions. And a 100-ad batch that came out perfect on the third try when it should have come out perfect on the first. Every prompt you write either has the constraint inside it, or you pay to find out. I paid. Don't.