workflow-ugc-automation — URL in, a reviewed batch of one-variable variants out
The transferable part of davidaistar's pipeline is the orchestration, not StarPop: brand DNA from a URL, a vision model breaking a reference into per-second beats, an LLM chunking those into ≤15 s prompts, one base fanned into variants, parallel generation, a review step. We rebuild it on our own scripts, API-first (no browser driving a SaaS UI), with our gates. Free steps can run unattended. Nothing paid runs without Fish's go for that batch, and nothing in this workflow writes to Meta.
1. Read first
knowledge/ad-formats/_FORMAT-CORE.md: doctrine §1, lanes §2, shot tiers §2b, lane limits §3 (wrist rule), gates §4. Binding. Photoreal people = ugc-omni and its G0-G4 gates (§7 here).knowledge/ad-formats/_MODEL-REGISTRY.md: every generation step below names a ROLE; the model comes from there. fal is the default gateway.references/procedure.md: commands, file schemas (brand-dna, scenes, variants, manifest, review), prompt template, catalog refresh, worked ILLUSTRATIVE example.references/sources.md: the davidaistar videos, what we kept and what we refused.- Brand:
knowledge/brands/dawnbands/product-truth.md(hardware facts),knowledge/brands/dawnbands/claims-register.md,knowledge/brands/dawnbands/brand-kit.md. - The format skill for the ad you're batching (
format-ad-clonefor the source-type decision;format-podcast,format-street-interview,ugc-omni,cartoon-h3for production rules).skills/ugc-omni/SKILL.md+scripts/ugc_omni/omni.pyare THE photoreal engine (Fish 10-06; another session owns them: read, never edit). A batch's photoreal base is one ugc-omni job (references/omni-job.example.json, illustrative). This workflow orchestrates; it never overrides a format's beat sheet.
2. When to use / when not
- Use when the job is a BATCH: one approved base ad → 3-12 variants, or one product URL → several formats tested in parallel, or a winner to fan out one variable at a time.
- Use step 1 alone (brand DNA) whenever a brief needs current product images, price, palette or PDP copy: it's free and read-only.
- Don't use for a single bespoke ad (go straight to its format skill), for natives (
dawn-native-brief→dawn-native-write), or to clone a lane-leader competitor's script (format-ad-clone: their dissect is a gap map). - Evidence status: davidaistar claims no CPA/ROAS for the automated outputs (f83UH: qualitative only). Our proof is the one-variable test design, not his numbers. The pipeline is untested end-to-end on Dawn; each lane under it has its own receipts.
3. Inputs
| Input | Required | Where it comes from |
|---|---|---|
| Product URL | yes | live PDP (Dawn default per workspace CLAUDE.md; it redirects, so take the canonical URL brand_dna.py records) |
| Reference ad | for dissect-led batches | our winner, a non-competitor organic/scaling ad, or a competitor (gap map only); file or URL |
| Approved base script | before any variant | format skill beat sheet → direct-response-copywriter doctrine → humanizer → red-team (core §1 copy routing) |
| Parent + its CPA | yes | the paying ad this batch iterates (core §5 parent:) |
| Actor reference | photoreal only | ugc-omni G1: a real creator frame → avatar; G2 locks the character's prompt |
| Fish's batch go | before step 6 | in chat, against the cost quote; recorded in manifest.json |
4. Outputs
All under output/ugc-auto/<YYYY-MM-DD>-<slug>/ (new slug per batch, session freshness):
- brand-dna.json copy (source of truth stays output/brand-dna/<handle>/, with images/ + raw/ receipts)
- dissect/ pointer to output/ad-dissect/<slug>/ (dissect.json, pacing.json, dissect.md)
- scenes.json: the base ad as ≤15 s generation units, each with role, tier, Action/Audio/Quality prompt blocks
- variants.json: the variant list, ONE axis + value per variant, the parent and what stays constant
- manifest.json: per variant per unit: lane, registry role, model id, job dir, cost quote, actual cost, takes to approved, QA result, file (review.md rolls these up into cost per approved variant, failed takes included: starpop seedance-2-5-ads, top-5-ai-video-generators-realistic-ugc)
- review.md + ads.json: Fish's review sheet and the hub payload; final/<variant-id>.mp4 (≤24 MB copies for the hub)
5. Process
- Step 0 — combo plan (feedback loop,
workflow-combo-loop):python3 scripts/combo_loop/plan.py --format workflow-ugc-automation --brand dawn→ pick one combo (a paying parent + ONE changed dimension) and paste its block into the pack front matter / job or spec JSON ("combo": {...}; no file →tags.py register --prefix):combo_avatarcombo_anglecombo_povcombo_authoritycombo_stakecombo_stake_oncombo_emotioncombo_root_causecombo_mechanismcombo_payoffcombo_devicecombo_formatcombo_parentcombo_variablecombo_ad_prefix(= the launched Meta ad-name prefix).python3 scripts/combo_loop/tags.py check <pack>must PASS before concept approval; the weekly refresh reads results back by that prefix.
| # | Step | Tool | Cost |
|---|---|---|---|
| 0 | Balances + registry check; monthly fal catalog refresh + per-role diff | higgsfield account status, kie credit GET, fal catalog curl (procedure §0) |
free |
| 1 | Brand-DNA intake | python3 scripts/brand_dna.py <product-url> |
free |
| 2 | Dissect the reference | python3 scripts/ad_dissector.py <src> --brand-context "<one line>" [--competitor] |
1 Gemini call (cents) |
| 3 | Chunk into ≤15 s scene prompts + tier each unit | python3 scripts/ugc_tools/scene_chunker.py --script <approved> --lane <lane> --subject "the mom" --dissect <dissect.json> --out $B/scenes.json, then Claude fills the <FILL> slots (procedure §3) |
free |
| 4 | Variants manifest (one swap each) + quote | python3 scripts/ugc_tools/batch_variation.py plan --base $B/scenes.json --axis <axis> --values <values.json> --price <lane>=<$>/s; cartoon lane uses python3 -m scripts.cartoon_h3.matrix |
free |
| 5 | Stage the PLAN as a hub draft; quote the cost; get Fish's go | ~/ad-batch-hub/stage-session.py <plan-dir> --brand "Dawn Band" --type ugc --draft --push |
free |
| 6 | Smoke one unit, then fan out per lane | lane entry points (§6), --spend / no --dry-run only after the go |
PAID |
| 7 | QA every output | scripts/gemini_qa.py, omni.py verify, clipqa.py, eyes |
cents |
| 8 | Review sheet + finalize the hub session | python3 scripts/ugc_tools/review_sheet.py --manifest $B/manifest.json (review.html + review.md), stage-session.py <batch-dir> --finalize <session-id> --push |
free |
| 9 | Hand to launch (not this workflow) | media-buyer; meta_preflight.py sanitize + check_links; meta_link_audit.py |
human-gated |
Step rules:
1. Brand DNA reads only the public storefront (<pdp> HTML, <pdp>.json, first-party theme CSS) with curl. It writes images, colours, fonts, PDP copy, price, review snippets and warnings. Act on every warning: a handle redirect means ad links use the canonical URL; lit-display images are never product-in-scene or i2v refs (use knowledge/brands/dawnbands/product-cutout-unlit.png); a price mismatch with product-truth means the live PDP wins for offer lines and Fish hears about it. Hardware facts still come from product-truth only; PDP review snippets are store-curated, not VOC.
2. Dissect gives structure and measured pacing. It does NOT give our script. Source-type decision per format-ad-clone: our winner / non-competitor = mirror the structure with one innovation; competitor = gap map, must fail the competitor-swap test.
3. Scene chunking: group beats into generation units that respect the lane cap (H3 ≤15 s/≤4 panels; omni buckets 4/6/8/10 s, one script line per take; Seedance b-roll per registry). Each unit carries the dissect's role, a tier (A/B/C, core §2b), and three blocks: Action (who, doing what, framing), Audio (voice, room tone, delivery, exact line), Quality (capture device, light, aspect). Give the model the scene + the line and let it improvise camera work; lock only what the gate checks (product, wrist, words). Every ref gets one stated job in a reference map, every shot one action and one camera instruction, every seam between shots is chosen (cut or stated transition), and footage comes back clean of captions/text (procedure §3; starpop Seedance/Kling guides).
4. Variants: one axis per variant vs the base: hook (first 3 s line + action), actor (one locked character swapped for another), setting (environment only), product_angle (which benefit/mechanism beat leads), delivery (same words, a different read: calm / tired / skeptical / deadpan; starpop how-to-use-seedance-2-0-to-make-ads, how-to-use-kling-3-0-to-make-ads). Everything else is byte-identical (same body units reused, not regenerated). Two axes changed = two variants. 3 hooks on a shared body is the default first fan-out (core §4 gate 2).
5. Draft first (hub rule): the plan (scenes, hooks, start frames if any) goes up as a Draft session; Fish reviews there. A missing verdict is NOT approval. Fish's go is a chat message naming this batch and its quote; write it into manifest.json go.
6. Draft → final + fan-out (core gate 6b): where the lane has a draft route (Seedance 2.5 Draft → "Generate in 1080p" on Higgsfield; fal Flux 3 */draft → draft-enhance; H3 480P → upscale only after a side-by-side vs native 1080), iterate tier-A/B units on drafts and render only approved drafts at final quality. ugc-omni has no draft step: its G2 lock (same line good 3×) is the smoke and the proof, and every A-roll take is Omni (tiers only pick b-roll). H3 cartoon batches draft with run.py --draft (480P, own <slug>-draft dir); no other lane has a batch draft stage (NOT BUILT). Then smoke ONE unit of the base on its lane, Fish's eye on it, then the rest. Shared body first, then hooks (shared-batch race, cartoon-h3). Regens are their own calls and count against a batch cap of 8.
7. QA: review in a fixed order and pick correct over pretty: product → face/wardrobe → hands/action → dialogue → continuity → camera → audio/ending. Before any regen, name the cause (reference, prompt, scope, settings) and change only that. Freezes 0, dark frames 0, transcript = script (omni verify), captions from the script, band dark and wrist down in every product shot, no smartwatch. A generated band appears only through ugc-omni's product gate (variant/pick --product ≥7/10 + display state, prodcheck), display-off wording, worn or resting across a palm (never pinched upright), product_action locked; real footage is the fallback. Gemini samples ~1 fps: eyes are the final gate.
8. Review sheet: one row per variant (procedure §8). Stage finals into the SAME hub session (--finalize), never a second session.
6. Tools & models
Roles come from _MODEL-REGISTRY.md; re-read it each batch. Lane per format:
| Format (his five) | Our lane + entry | Registry role(s) | Status |
|---|---|---|---|
| Handheld selfie talking head | ugc-omni scripts/ugc_omni/omni.py (cost, lock, gen, verify, broll, assemble; dry run until --spend) | Photoreal / multi-ref video with locked character; Image gen (avatar/frames) | Built (another session owns ugc-omni: reference, don't edit) |
| One-shot UGC with b-roll | ugc-omni A-roll + broll; or Seedance b-roll scripts/seedance_broll_runner.py | + Cheap simple-motion i2v (tier C) | Built. Runner models are Higgsfield seedance1_5/seedance_2_0 only |
| Single-take native speech (yapper) | ugc-omni mode: oneshot (one location, no b-roll, chained segments on exact frames, anchor_every: 3, reveal via end_frame) | Photoreal talking take (Omni) | Built. H3 native voice via yapper.py = side arm only (one variable vs a locked omni baseline); its keyframe chain stays closed (Fish 10-05) |
| Pixar / cartoon object | python3 -m scripts.cartoon_h3.run (+ matrix for hooks × leads) | Stylized video, ref + audio clock; TTS | Built, full gates in cartoon-h3; no lip-sync (core §3): his talking-object shot is staged off-mouth |
| Podcast two-shot | format-podcast | per that skill | per that skill |
| Statics from the same DNA | scripts/statics/gen_statics.py (dawn-statics) | Image gen; Exact text on image | Built |
| Cinematic short (no VO) | not run | — | Skipped: he rates it weak for DR (f83UH) |
Provider conflicts, flag once per batch (not resolved here): (1) the registry says fal is the default gateway, but seedance_broll_runner.py is Higgsfield-only (seedance1_5/seedance_2_0, no 2.5) and skills/vo-broll/SKILL.md (9-15) routes generated clips to Higgsfield; (2) ugc-omni runs on kie, not fal; (3) image arms: pass --arms gpt2,sd5 (Fish 10-06: Nano Banana retired, Seedream 5 Pro is arm 2; omni.py's own default still lists nbp, its owner changes that). Ask Fish at the batch go which applies.
Shared: dissect + QA = Video understanding (scripts/ad_dissector.py, scripts/gemini_qa.py, scripts/cartoon_h3/clipqa.py); scripts = LLM writing (scripts/claude_cli.py); staging = ~/ad-batch-hub/stage-session.py, stats = ~/ad-batch-hub/scripts/refresh.sh (read-only Meta GET); cartoon audit site = python3 -m scripts.cartoon_h3.hub.
NOT BUILT (needs Fish's go):
- Built 10-06 (free, scripts/ugc_tools/): scene_chunker.py, batch_variation.py (plan / approve / check / log; check exits 2 without Fish's go), review_sheet.py. Not built: per-lane job dirs from a variant (each lane's own entry point still runs the units: matrix.py, omni hook lines, gen_statics --only), and no lane runner calls batch_variation.py check yet.
- Seedance 2.5 (fal) in seedance_broll_runner.py; Video extend, Video edit by instruction, Motion transfer/recast roles (all NOT BUILT in the registry).
- Static swipe-file library (his 136 ripped templates) under knowledge/.
- A fal catalog refresh/diff script (procedure §0 is a curl loop + a python one-liner).
- Browser-driving a generation SaaS (his Claude-in-Chrome step): deliberately not built; API entry points replace it.
7. Gates & QA checklist
- [ ] Brand DNA run today; every warning acted on (canonical URL, lit images excluded, price mismatch told to Fish).
- [ ] Parent named with CPA; batch purpose + constant + variable written (hub
test_design). - [ ] Base script went through the format beat sheet + copy routing; problem-aware line 1 names HER by a symptom; stake on her, caused by the mornings; recipe A (story: mom narrator, accuser, explainer inside the story, vindication) or recipe B (callout: brand voice, her daily burden, kid-independence payoff) per the format.
- [ ] Competitor source → gap map only; concept fails the competitor-swap test.
- [ ]
variants.json: every variant changes exactly ONE axis; constant units reused, not regenerated. - [ ] Every unit ≤ its lane cap; tier set; tier-A only on product/hands/CTA/hook.
- [ ] Photoreal units = ugc-omni G0-G4 (same in format-podcast / -street-interview / -ai-actor-demo / -motion-transfer / -whistleblower / -reaction-duet): G0 script + hooks +
board(no paid step before it) → G1 real ref frame + avatar (+ gated product frames) → G2 lock: same line good 3× (the 9-30 motion proof) → G3 every A-roll line + b-roll + product check → G4 the assembled cut. Every gate is Fish's; the batch go is on top. Anactorvariant = a new G1 + G2. No face-blur or filter workarounds. - [ ] Generated band frames only through ugc-omni's product gate (
pick --product≥7/10, right display state); everything else real footage. Seedance 2.5 product shots stay rejected (Fish 10-06). - [ ] Draft → final run where the lane has a draft route (step 6); tier recorded per unit in
manifest.json. - [ ] Draft staged; Fish's verdicts pulled; Fish's batch go recorded with the quote; balances checked (fal, Higgsfield, kie).
- [ ] Smoke unit approved by eye before the fan-out; regens ≤8;
--candidates 1; never Higgsfield UI Rerun. - [ ] QA: script-true transcript, captions from script, band dark + wrist down, no lit face, freezes 0; Fish watched.
- [ ] Hub finalized in the same session; nothing sent to Meta from this workflow.
8. Failure modes
- Wrong product page: an old handle silently redirects (Dawn's default PDP does) → links point at the redirect; use
canonical_url. - Lit-band refs: the PDP hero and colour shots show a lit display → H3/Seedance draws a smartwatch. Product refs = the permanent real-photo set
knowledge/brands/dawnbands/product-refs/(Fish 10-06):pack_off.jpgby default,pack_on.jpgonly when the lit time is the point; never PDP renders. - Hand/product fidelity: ungated band-in-hand scored 3-4/10 (yapper 10-04) → product frames go through ugc-omni's gate (exact first frame scored vs the real pack; close framing;
product_actionlocked, else the hand drops at 0.25 s) or real footage. A new model candidate gets a one-unit proof first. - Variant creep: hook + actor changed together → the read is meaningless. Split.
- Accidental spend:
seedance_broll_runner.pysubmits unless--dry-run; omni/run.py are dry until--spend. Always quote first. - Voice drift across units: separately generated takes change voice → one locked prompt/voice per actor (omni
lock/voice), one voice pass over all units. - Price drift: product-truth said $39.99 while the live PDP said $44.99 (10-06) → offer lines from brand-dna, Fish told.
- Silence read as approval on the hub → only explicit verdicts + a chat go count.
- Parallel race: hooks generated before the shared body finishes → duplicate paid body batches.
9. Sources
~/research/davidaistar/REPORT.md; analysis/gemini_f83UH-qmNUs.md, analysis/gemini_aqo2L5wYGEg.md, analysis/ugc_seedance.md (f83UH, aqo2 sections, cross-video #6-7), analysis/guide_copy_images_shorts.md (shorts wGVBPiaFYiw, cPQ9e0RfiQE, J7k_eNpLO3o, 2zOlhDAOwb4; variation mode), analysis/diff_ai_ugc.md, analysis/diff_cloning.md. Ours: project_ad_batch_hub.md, feedback_higgsfield_credit_discipline.md, feedback_meta_upload_preflight.md, feedback_meta_link_match.md, reference_shopify_stores.md (public storefront only). Detail: references/sources.md.
Not built yet (needs Fish's go)
- 6. Draft → final + fan-out (core gate 6b): where the lane has a draft route (Seedance 2.5 Draft → "Generate in 1080p" on Higgsfield; fal Flux 3 /draft → draft-enhance; H3 480P → upscale only after a side-by-side vs native 1080), iterate tie
- NOT BUILT (needs Fish's go)
- Built 10-06 (free, scripts/ugc_tools/): scene_chunker.py, batch_variation.py (plan / approve / check / log; check exits 2 without Fish's go), review_sheet.py. Not built: per-lane job dirs from a variant (each lane's own entry point still ru
- Seedance 2.5 (fal) in seedance_broll_runner.py; Video extend, Video edit by instruction, Motion transfer/recast roles (all NOT BUILT in the registry)
- Browser-driving a generation SaaS (his Claude-in-Chrome step): deliberately not built; API entry points replace it
- Changed / refused: no motion swap (registry Motion transfer role is NOT BUILT); no uncensored-model routing; no Sora-style "remix" of someone else's video
- analysis/diff_cloning.md: P0 dissector (built: scripts/ad_dissector.py); P1 batch_variation.py (built 10-06 as scripts/ugc_tools/batch_variation.py, scene-set variants, not image batches) + static swipe archive (NOT BUILT); competitor disse
- everything-you-need-to-know-about-seedance-2-0: extension length = the new segment, not the total; up to 12 input files with 15 s combined video/audio refs. Registry-level (Video extend role NOT BUILT); proposed note there