From e7ed8efb7cae79c166abeb34f8def887210184bb Mon Sep 17 00:00:00 2001 From: Joel Horwitz Date: Tue, 4 Aug 2026 14:19:27 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20v0.4.0=20=E2=80=94=20RSA=20Ad=20Strength?= =?UTF-8?q?=20asset=20floor=20(11+=20headlines=20/=204=20descriptions,=201?= =?UTF-8?q?5/4=20recommended)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Google Ads API accepts as few as 3 headlines and 2 descriptions, but RSAs built at that minimum score Poor on Ad Strength and get throttled in the auction. Enforce a real floor everywhere the plugin creates or reviews ad copy: - ad-copy-generation: 11/4 minimum, 15/4 recommended, every slot filled, ad-group keywords echoed in 5+ headlines, no filler padding, no truncation - campaign-preflight: new blocking creative-asset-completeness check; Ad Strength POOR/AVERAGE = fix before launch; same fill-every-slot rule per platform at its own ceiling - creative-director + media-buyer agents carry the floor rule Co-Authored-By: Claude Fable 5 --- .claude-plugin/plugin.json | 2 +- CHANGELOG.md | 9 +++++++++ agents/creative-director.md | 1 + agents/media-buyer.md | 1 + skills/ad-copy-generation/SKILL.md | 3 ++- skills/campaign-preflight/SKILL.md | 12 ++++++++++-- 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b2f977c..e9500e7 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -3,7 +3,7 @@ "name": "synter", "displayName": "Synter", "description": "The AI Agent Operator for Ads. Connect every ad platform, build audiences from signals and first-party data, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — all in one conversation. You direct, the agents execute. Nothing ships without your approval.", - "version": "0.3.0", + "version": "0.4.0", "license": "MIT", "author": { "name": "Synter", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6caeb2d..01e0f9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to the Synter plugin are documented here. This project follows [semantic versioning](https://semver.org). +## [0.4.0] — 2026-08-04 + +RSA asset floor: the Google Ads API accepts as few as 3 headlines and 2 descriptions, but ads built at that minimum score "Poor" on Google Ad Strength and get throttled in the auction. The plugin now enforces a real floor everywhere ad copy is created or reviewed. + +### Changed +- **`ad-copy-generation`** — RSAs require **11 headlines / 4 descriptions minimum (15/4 recommended)**, every slot filled, the ad group's actual keywords echoed in at least 5 headlines, no filler padding, no truncation (rewrite to the character limit instead). +- **`campaign-preflight`** — new blocking "Creative asset completeness" check: per-ad asset counts read from the platform and compared against the floor; Google Ad Strength POOR/AVERAGE must be fixed before launch; the same fill-every-slot rule applies on every platform at its own ceiling. +- **`creative-director`** and **`media-buyer`** agents — carry the floor rule wherever they direct ad creation or launch. + ## [0.3.0] — 2026-07-25 Fourteen new skills deepening diagnostics, measurement, and creative coverage — every one grounded exclusively in public Synter MCP tools. diff --git a/agents/creative-director.md b/agents/creative-director.md index b6200cc..21714f3 100644 --- a/agents/creative-director.md +++ b/agents/creative-director.md @@ -15,5 +15,6 @@ Method: Non-negotiables: - Brand voice: terse, certain, doing the work — show, don't sell. Approved CTAs and verbs only. No banned language ("AI-Powered", "seamless", "revolutionary", "Try the beta", "Start Your Free Trial Today!", and the rest of the forbidden list). - Accuracy over polish: never fabricate a price, competitor, statistic, or claim. No real number → say so; don't invent it. +- Fill the format: RSAs ship with 11+ headlines and 4 descriptions (15/4 recommended, never the API minimum of 3/2), with the ad group's actual keywords echoed in at least 5 headlines — under-filled asset sets score "Poor" on Ad Strength and get throttled. No filler lines; rewrite to the character limit rather than truncate. - Policy: pre-check against the target platform's ad rules and flag anything restricted. - Generating is safe; attaching creative to a live, spending campaign is an action the user approves. diff --git a/agents/media-buyer.md b/agents/media-buyer.md index 7c22629..aea44cd 100644 --- a/agents/media-buyer.md +++ b/agents/media-buyer.md @@ -11,6 +11,7 @@ Discipline: - Confirm the target before touching anything: `list_connected_accounts`. Use the exact account IDs the tools return — never invent or guess an ID. Never run one org's campaign through another org's key. - Confirm conversion tracking exists and is attached before launch (`ga4_list_conversions`, `get_gtm_tag`, `verify_pixel_ownership`). No tracking → stop and fix it. - Run preflight every time: geo correct, exclusions applied, tracking firing, budget and bid caps sane (guard hard against fat-finger budgets — a daily budget 10–100x intended is the classic incident), creative policy- and brand-clean, final URL resolves and matches the ad. +- Creative floor before launch: every RSA carries 11+ headlines and 4 descriptions (15/4 recommended — never the API minimum of 3/2), keywords echoed in the headlines. Google Ad Strength POOR or AVERAGE means fix the assets first, not launch and hope. - Build with `create_campaign_plan` / `create_campaign_for_audience` / `execute_campaign_plan`; go live with `enable_campaign`. For multi-platform, ship each platform and confirm each with its real campaign ID. - Porting an existing campaign to another platform (e.g. "build my Google campaign on Microsoft"): read the source structure first (`list_campaigns`, `run_gaql_query` for Google), map it to the target platform, then build. Carry over geo, match types, and negatives, not just keywords. This is the **replicate** workflow; the per-platform mapping lives in the **platform-google** / **platform-microsoft** / **platform-meta** / **platform-linkedin** and sibling playbook skills. diff --git a/skills/ad-copy-generation/SKILL.md b/skills/ad-copy-generation/SKILL.md index 088c3a9..11fb79d 100644 --- a/skills/ad-copy-generation/SKILL.md +++ b/skills/ad-copy-generation/SKILL.md @@ -11,7 +11,7 @@ Apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md` to ev ## Fit the format -- **Search (Google, Microsoft) RSAs:** up to 15 headlines and 4 descriptions. Vary them so the platform can assemble strong combinations; pin only when a legal or brand line must always show. Lead with the keyword intent and the offer. +- **Search (Google, Microsoft) RSAs:** the API accepts as few as 3 headlines and 2 descriptions — never ship that. Write **11 headlines and 4 descriptions minimum; 15 headlines and 4 descriptions recommended**. Fill every slot: under-filled RSAs score "Poor" or "Average" on Google Ad Strength and get throttled in the auction. Echo the ad group's actual keywords in at least 5 headlines — the platform grades relevance against the real keyword list, not a paraphrase. Vary the rest so the platform can assemble strong combinations; pin only when a legal or brand line must always show. Lead with the keyword intent and the offer. Never pad with filler lines to hit the count, and never truncate a strong line to fit — rewrite it to the character limit instead. - **Social (Meta, TikTok, Reddit, X):** primary text plus a hook that earns the first second. Match the platform's native tone; a LinkedIn line is not a TikTok line. - **LinkedIn:** professional, outcome-led, decision-maker framing. @@ -32,5 +32,6 @@ Run every variant through the **ad-policy-compliance** thinking and the brand fo ## Rules - On brand and on policy, every line. No forbidden words, no invented claims. +- RSAs ship with 11+ headlines and 4 descriptions (15/4 recommended) — the platform minimum of 3/2 is a floor for the API, not for a real ad. At least 5 headlines echo the ad group's actual keywords. No filler, no truncation. - RSAs get varied, unpinned assets unless a line must always appear. - Report the variants and the one variable each set is built to test. diff --git a/skills/campaign-preflight/SKILL.md b/skills/campaign-preflight/SKILL.md index 339ee48..1ea3468 100644 --- a/skills/campaign-preflight/SKILL.md +++ b/skills/campaign-preflight/SKILL.md @@ -44,7 +44,15 @@ A campaign with no location criteria serves everywhere, including low-quality an - Prefer presence-based matching (people in the location) over interest-based unless the goal truly needs it. - Verify click geography matches intent after launch, not just the configured setting. -## 5. Negatives, budget, and schedule (warn) +## 5. Creative asset completeness (blocking for Search RSAs) + +A structurally complete campaign can still launch handicapped: an RSA created with the platform-minimum 3 headlines and 2 descriptions is accepted by the API but scores "Poor" on Google Ad Strength and gets throttled in the auction. + +- Read each ad's actual asset counts (Google: `run_gaql_query` on `ad_group_ad` with the RSA headline/description fields). Compare against the floor: **11 headlines / 4 descriptions minimum, 15 / 4 recommended** per RSA. +- Check Google's Ad Strength rating where available. **POOR or AVERAGE = fix the assets before launch**, not after. Add real, keyword-echoing headlines — never filler lines written just to raise the count. +- Apply the same completeness check on every platform at that platform's own ceiling: fill the ad format's available text and creative slots (e.g., Microsoft RSAs mirror Google's 15/4; social formats get all placements' aspect ratios and full primary-text/headline slots), don't ship the minimum the API will accept. + +## 6. Negatives, budget, and schedule (warn) - **Negatives** on Search: confirm a negative list exists so new match surface does not leak spend. See the **negative-keywords** skill. - **Budget:** sane daily amount, guarded against a fat-finger 10 to 100x. Set a max CPC on manual bidding. @@ -52,4 +60,4 @@ A campaign with no location criteria serves everywhere, including low-quality an ## Report -Output a short table: each check as PASS, WARN, or FAIL with a one-line note, then a clear recommendation (enable, or fix these first). Blocking fails (structure, tracking, UTMs, geo) must clear before you enable. Then, on the user's go, `enable_campaign` and confirm live with `list_campaigns`. +Output a short table: each check as PASS, WARN, or FAIL with a one-line note, then a clear recommendation (enable, or fix these first). Blocking fails (structure, tracking, UTMs, geo, under-floor creative assets) must clear before you enable. Then, on the user's go, `enable_campaign` and confirm live with `list_campaigns`.