| name | remix-ad |
| description | Remix an ad already uploaded to the Goose Ads library into a NEW ad — same proven structure, retargeted to a different brand/product (or the same brand, new angle). Reads the sample's remix payload (recipe shot-list, extracted_script, how_to, and remix_spec of atom-skills / voices / worlds / characters), decides what to KEEP vs SWAP, rewrites the script for the new brand while preserving the beat structure and timing, then routes to the matching production template (animated-explainer-ad, create-podcast-skit-ad, …) and runs the kept atom-skills in recipe order. The consumer counterpart to the upload-ad-sample skill. Use when the user says "remix this ad", "make this for [other brand]", "same format, new brand", or references a library sample to riff on. Needs a Goose Ads API token; regeneration needs the routed template's keys. |
remix-ad
Take a winning ad that's already in the Goose Ads library and make another one
from it — usually for a different brand, sometimes the same brand with a new
angle. The library sample carries a remix payload (written by the
upload-ad-sample skill); this skill reads it, keeps the proven skeleton, swaps
the brand-specific layer, and regenerates. It's the consumer end of the
upload↔remix loop.
This skill is a planner + router: it fetches the payload, makes explicit
keep-vs-swap decisions, rewrites the script, and hands the actual generation to
the production template that made the original (e.g. create-podcast-skit-ad).
When to use
- "Remix this ad for [other brand]" / "make the laundromat podcast ad but for [X]."
- "Same format, new brand" / "riff on sample
<id>" / "give me a new cut of this."
- Any time a library sample's structure is worth reusing on fresh brand content.
Do NOT use to: research a brand from scratch (use brand-research), or make an
ad with no source to remix (use a production template directly).
What it reads — the remix payload
Full contract in references/remix-payload.md. The library has no GET-by-id
route, so fetch_sample.py lists samples and filters by id/title. Each sample
carries: recipe (shot list + timing), extracted_script, skills_used,
how_to, and remix_spec (skills, voices, worlds, characters).
The skills are atoms, never a molecule. remix_spec.skills / skills_used
list atomic capability slugs (generate-voiceover, generate-broll-shot,
compose-master). Run exactly those atoms in recipe order — never substitute a
molecule, which would over-specify and re-pull steps the source didn't use.
Keep that discipline when you re-upload the remix.
Keep vs swap (default policy)
| Layer | Default | Notes |
|---|
recipe (format + beat arc) | KEEP | The proven format (e.g. two-host skit) and beat arc (hook → mechanism → payoff). The recipe is a reference; the new ad's scene count flexes with the new brand's copy density. |
remix_spec.skills (atoms) | KEEP | Run in recipe order to regenerate. |
how_to gotchas | KEEP | Carry forward. |
| script copy | SWAP | Rewrite freely for the new brand within the source's format + beat arc. Do NOT preserve the source's literal scene count — the new brand's mechanism complexity / copy density determines how many scenes it needs. |
| brand assets (wordmark, product, end card) | SWAP | New brand's real assets. Never AI-render brand text. |
| voices | keep (swap on request) | Reuse the selected voice unless the new brand needs a different read. |
| world/set | keep (swap on request) | Reuse the set, or pick a new themed one. |
| characters | swap (keep on request) | Usually recast for the new brand; reuse a soul_id only if the persona transfers. |
Setup
pip install -r requirements.txt
cp .env.example .env
The regeneration runs a production template — install ITS deps/keys when you reach the route step.
Workflow
-
Get the source payload — local-chain OR library. Two cases:
Review the source's printed summary or the JSON itself: recipe shots,
formatProfile, atom-skills, voices/worlds/characters, variant_assets[]
audit results, production_scripts[].
-
Intake the target — ASK explicitly for the four swap dimensions.
Use AskUserQuestion (or equivalent in the harness) to get:
- World: keep the source's set, or swap to a new themed set?
- Angle: one-line creative direction. If
clients/<slug>/concept-brief.md
exists, surface its top concepts as the option list rather than
inventing.
- Voices: keep / swap which voice_ids? (One row per source voice.)
- Characters: keep / swap / recast per source character (by sourceKey).
Do NOT infer these from a one-line product description. The user's
brief is usually skeletal — every Som and Hume run hit this and asked
the same four questions; standardize.
Get the new brand + product (+ guardrails). If the new brand has no
context yet (ls clients/<slug>/brand-research/ returns nothing — check
case-insensitively: clients/Hume-health/ and clients/hume-health/
are the same brand), run the brand-research skill first and pass its
brand_dir.
Brand-pack-current-SKU wins over the user's one-liner. If the user
says "can on nightstand" but 01-company-overview.md flags the can line
as EOL, use the current SKU and flag the swap. No question unless they
explicitly override.
Decision rules
-
Keep the format + beat arc; honor the source's sceneCount property.
Read source-sample.json.formatProfileProperties.sceneCount:
"flexible" (most formats) — scene count flexes with the new brand's
copy density. The archetype + arc are what's proven, not the literal
count.
"locked-to-source" (any format where audio meter sets timing — sung
music, scored cinematic, etc.) — mirror the source 1:1. Flexing would
desync the audio.
null or missing — surface to the user.
-
Atoms, not molecules. Run the exact atoms in remix_spec.skills, in recipe order. Never expand a molecule.
-
Lock timeline to the new VO, never the source duration — new copy runs to a different length. (Exception: locked-meter sung music.)
-
New brand's guardrails win. Re-apply claim/competitor limits for the target brand, not the source's.
-
Brand text composited, never AI-rendered — new wordmark + end card.
-
Confirm the routed template with the user before paid generation — unless the operator's prompt already names a format that uniquely matches the inferred template (e.g. "Ladder podcast skit" → create-podcast-skit-ad). Then skip the confirm.
-
For molecule routes without an executable driver (e.g.
create-podcast-skit-ad) OR when route_to_template = (confirm-with-user):
port the source run's working/*.py scripts and adapt brand-specific
paths. The molecule SKILL.md is a recipe, not executable; the source's
driver scripts are the closest runnable template. Read
source-sample.json.production_scripts[] to find them.
Common adaptations when porting:
REPO_ROOT path depth. Older drivers used
Path(__file__).resolve().parents[3]; the current
clients/<brand>/ad-runs/<run>/working/ layout needs parents[4].
Better: anchor on .git / CLAUDE.md presence. See
[[feedback_repo_root_parents4_clients_layout]].
- End-card brand assets (logo, hero PNG) → swap to the new brand's
assets after the audit below.
- Caption color → wire through the flag (see below).
Output
./remix/source-sample.json — the fetched payload.
./remix/remix-plan.json — keep/swap decisions + atom-skill order + routed template.
- A rewritten script for the new brand.
- The new ad master (produced by the routed production template), optionally re-uploaded to the library.
Failure modes
- No GET-by-id → expected;
fetch_sample.py filters the list. If --title matches >1, it asks for --id.
- Sample has no remix payload (
recipe/remix_spec empty) → it was a bare showcase upload; you can still mimic from media_url by watching it, but there's no structured recipe to keep — tell the user.
- Routed template guess is wrong → override it in
remix-plan.json before generating.
- Source voice doesn't fit the new brand → re-run the plan without
--keep-voices and recast.
- Auth 401 →
GOOSEWORKS_API_TOKEN missing/invalid.
Provider-specific gotchas (each costs ~20-30 min to debug from scratch)
-
Higgsfield CLI: --medias <path> rejected as "should be array, got
string" → the right flag is --start-image <path> (per
higgsfield generate create --help's media-flag list).
higgsfield model get <model> lists medias array as a param but the
CLI translates per-model — --help is authoritative. See
[[feedback_higgsfield_cli_media_flag]].
-
Higgsfield: not_enough_credits mid-batch → switch workspace
first (higgsfield workspace list → set <id> to one with credits;
Gooseworks is usually the right one but verify, balances drift). On
full exhaustion, spillover to create-image-nano-banana-2-fal
($0.08/image, same quality for character variants). See
[[feedback_higgsfield_burst_credit_reserve]] and
[[reference_higgsfield_gooseworks_workspace]].
-
fal subscribe failed with truncated stderr → re-run ONE clip via
the atom's generate.py directly to surface the chained
FalClientHTTPError. See [[feedback_fal_subscribe_error_envelope]].
-
fal upload returns generic error past 10MB → fal caps uploads at
10MB. sips -Z 1536 <file> downscales the longest side to 1536px
(or ffmpeg -vf scale=1536:-2). See [[feedback_fal_10mb_upload_cap]].
-
Groq Whisper 403 / error code: 1010 → Cloudflare blocks default
Python urllib User-Agent. Add User-Agent: <app>/<version> and
Accept: application/json headers on every Groq request. See
[[feedback_groq_whisper_cloudflare_ua]].
-
Whisper transcribes sung music as 🎵 Music Playing 🎵 →
expected whenever audioType === "sung-music". Drive captions from
script.json scene windows instead. See
[[feedback_subtitles_from_whisper_word_level]] Case B.
-
ElevenLabs Music 422 on music_length_ms + composition_plan together
→ drop music_length_ms. Composition plan section durations sum to the
total. See [[reference_elevenlabs_music_api_quirks]].
-
ElevenLabs Music words_timestamps come back empty / null on sung
mixes → expected. The flag is unreliable for
atmospheric music. Drive captions from scene windows.