How to review a live Google Ads account periodically: split the work into independent parallel workstreams, consolidate and cross-check their findings, then turn them into ranked change proposals and manual tasks.
BigData Boutique defaults: B2B lead gen (consulting/services), long sales cycle, low conversion volume, currency ILS, cost fields in micros (÷ 1,000,000). Thresholds assume lead gen. ROAS variants are noted where they differ.
1. Principles
- Measurement first: W1’s verdict gates every CPA/ROAS finding (§7.1).
- Business outcomes over platform conversions: judge on cost per SQL/opportunity/pipeline when CRM data exists (W12).
- Lag-complete periods only: conversions are reported on the click date, so recent days are under-reported.
- Enough data, small steps: act only past §9.1 thresholds; ≤1 spend-affecting change per campaign per cycle.
- Every finding cites data (entity, metric, window, number). No generic advice.
- Google recommendations are inputs. Optimization score reflects adoption of Google’s recommendations, not performance.
- Humans approve every change. Reviews only propose (§8).
2. Cadence & windows
| WS | Weekly (tactical) | Monthly (+) | Quarterly (+) |
|---|---|---|---|
| W1 Measurement | each primary action converted in last 7d | action inventory, duplicates, goals | enhanced conv., consent, offline import, attribution |
| W2 Settings | change history, disapprovals | auto-apply, networks, geo, language | access, verification, naming, dormant objects |
| W3 Budget/IS | pacing, limited by budget | IS lost budget/rank trend | marginal CPA, reallocation |
| W4 Bidding | learning/limited status | target vs actual | strategy fit vs volume, portfolios |
| W5 Keywords | — | spend w/o conversions, QS | duplicates, zero-impr. cleanup, structure |
| W6 Search terms | yes (highest-ROI weekly task) | intent tiering | over-negation audit |
| W7 Ads/assets | disapprovals | asset performance | full RSA/asset coverage, refresh plan |
| W8 Landing pages | broken URLs | CVR by page | CWV, message match, forms |
| W9 Segments | — | device/geo/hour anomalies | full segment review incl. demographics |
| W10 Type fit/PMax/AI Max/DG | search terms (if live) | channel split, asset groups | campaign-type fit, brand exclusions, incrementality |
| W11 Competition | — | auction insights trend | keyword-gap scan |
| W12 Lead quality | — | lead→SQL by campaign | pipeline ROAS, offline value calibration |
Windows: weekly = 7d + 28/30d context (label incomplete trailing data); monthly = 30d vs prior 30d; quarterly = 90d vs prior 90d (+ same period last year if seasonal). All lag-complete: if 90% of conversions arrive within L days, the window ends L days ago.
3. Execution model
Phase 4 Follow-up on LAST cycle (runs first) ─ did applied changes deliver? (§8.5)
Phase 0 Context pack (sequential, small) ─ goals, targets, windows, lag, entity map
Phase 1 W1…W12 (PARALLEL, independent) ─ each pulls its own data, returns Findings[]
Phase 2 Consolidation (sequential) ─ W1 gate, cross-checks, dedupe, root causes
Phase 3 Prioritize & act (sequential) ─ score, propose changes, create manual tasks, report
Workstreams read only the context pack and their own API data. They never propose; they return
candidate_actions. Proposals are filed once, after consolidation, so conflicting or duplicate
changes are never filed.
4. Phase 0: Context pack
| Item | Source |
|---|---|
customer_id, currency, timezone | task fields / customer (A1) |
| Goal per campaign (leads, SQLs, sales, awareness) | goals docs / task |
| Target CPA/tROAS and max allowable CPA (LTV × close rate) | goals / human |
| Brand terms (incl. products, misspellings); competitor brands | human / auction insights |
| Conversion lag profile (days to 90% of conversions) | segments.conversion_lag_bucket (A4) |
Windows current, previous, context (identical for all workstreams) | computed |
| Entity map: campaign id, name, type, status, bid strategy, budget | campaign (A5) |
| Known events: launches, site/tracking changes, holidays, outages | change history + human |
Last cycle’s change tasks + their expected_impact | Paid Ads board, label google-ads-change (§8.5) |
| Human policy decisions (e.g. “always bid on brand”) | goals / task |
5. Phase 1: Workstreams
Severity: 🔴 critical (money burning / data wrong) · 🟠 material · 🟡 hygiene. Thresholds marked [Rn] are defined once in §9.1.
W1 Measurement & conversion integrity
Data: customer (A1), conversion_action (A2), conversion goals, daily conversions per action
(A3), lag buckets, upload diagnostics (UI if not in API).
- Auto-tagging off 🔴 (GCLID needed for GA4 and offline import).
- Primary actions = real business outcomes. Micro-conversions (page views, scroll, newsletter) primary next to real goals steer bidding to junk 🔴. Make them secondary.
- Duplicates (GA4 import + Google tag both primary, double-firing thank-you page). Symptom: conversions ≈ 2× CRM leads 🔴.
- Counting leads = One, purchases = Every 🟠. Values differentiated, not uniform placeholders 🟠. Lookback matches the sales cycle (B2B often 60–90d) and attribution is data-driven 🟡.
- Recency: a primary action with no conversions in its normal cadence = broken tag 🔴.
- Volume for bidding per campaign/30d vs [R1] → feeds W4.
- Enhanced conversions for leads on and healthy 🟠.
- Offline import (CRM → MQL/SQL/Opp/Won) recent, low error rate. Since June 15, 2026 Google Ads API click-conversion uploads fail for developer tokens with no prior upload history; new integrations must use the Data Manager API. Confirm uploads still land 🔴 if stalled.
- Consent Mode in place if EEA/UK traffic 🟠.
- Reconciliation: platform conversions vs CRM leads, gap > [R2] → investigate.
Required verdict: measurement_confidence: high | medium | low + affected actions/campaigns.
W2 Settings, governance & policy
Data: campaign settings (A9), campaign_criterion, change_event (A10), recommendations
(A15), ad_group_ad.policy_summary (A11), auto-apply, access and verification (UI).
- Change history grouped by user and
client_type(UI, API, scripts, Google auto-apply). Flag unowned and auto-applied changes. Record dates for X11. - Auto-apply recommendations: broad-match upgrades, bid-strategy, keyword-removal or budget auto-apply without a deliberate decision 🟠.
- Search partners (on by default for Search campaigns): keep only if W9 shows CPA ≤ Google Search 🟠. Display expansion on Search campaigns 🟠.
- Location option “Presence or interest” on a fixed-market service leaks spend 🟠. Location targets/exclusions and languages match the market 🟡.
- Tracking template: with URL expansion it must use
{lpurl}🟠. Ad schedule vs ability to respond, ad rotation “Optimize” unless testing 🟡. - Policy: disapproved / approved (limited) ads and assets 🔴 on top spenders.
- Limited ad serving: expanded to more Search scenarios (June 2026) and to all Google Ads (Aug 2026), rolling out gradually through 2028. Check for notices; clear own-brand naming in ads and landing pages is Google’s stated mitigation 🟠.
- Quarterly: access hygiene (ex-vendors, least privilege, MCC/GA4/YouTube/Merchant links), advertiser verification, dormant objects (paused 90+d, ad groups without ads), naming convention 🟡.
W3 Budget, pacing & impression share
Data: campaign_budget, daily cost, campaign.primary_status(_reasons), IS metrics (A5).
- Pacing: a campaign can spend up to 2× its average daily budget on a day, but is never charged more than 30.4× per month. Flag projected month spend outside [R3] 🟠.
- Limited by budget → efficiency decides (X1/X2):
- lost IS (budget) high + CPA ≤ target → scale candidate ([R4] step);
- lost IS (budget) high + CPA > target → no budget; fix targeting/queries first;
- lost IS (rank) high → Ad Rank (quality, bid, assets). Budget won’t help → W5/W7/W11.
- Brand IS below [R5] 🟠; competitor taking brand traffic 🔴.
- Spend share vs (qualified) conversion share per campaign / shared budget → reallocation. Quarterly: marginal CPA (weekly spend vs conversions) vs max allowable CPA.
- Post-Aug 17, 2026: budget-limited tCPA/tROAS campaigns now bid to the target instead of over-performing it (Appendix B). Compare CPA before/after on these campaigns; W4 owns the fix.
W4 Bidding strategy & targets
Data: campaign.bidding_strategy_type, target fields (A5), bidding_strategy (portfolios),
campaign.bidding_strategy_system_status, simulators (campaign_simulation,
ad_group_criterion_simulation).
-
Strategy fits volume (internal ladder, uses [R1]):
Conv./campaign/30d Fit < 15 Maximize Conversions (no target), or pool via portfolio / consolidation 15–30 Maximize Conversions + loose tCPA (≈ actual +10–20%) 30+ tCPA, or tROAS if values are real and differentiated offline values + 30+ value events Maximize Conversion Value / tROAS on pipeline values -
Maximize Clicks on lead gen with conversion data 🟠.
-
Target realism: anchor to max allowable CPA and actuals. Gap > [R6] → “limited by target” (too low) or overpaying (too high).
-
Budget-limited after Aug 17, 2026: if actuals were well below target, lower the target toward real breakeven ([R4] steps) or drop the target. Google advises waiting 1–2 conversion cycles before judging, and does not adjust targets for you.
-
Status: learning / limited / misconfigured (e.g. no conversion actions in goal) 🔴.
-
Learning resets: repeated large target/budget changes or strategy switches → recommend a freeze.
-
Bid adjustments under Smart Bidding are not used, except device −100%. Leftovers are noise 🟡.
-
Data exclusions must cover tracking-outage dates, or Smart Bidding learns from bad data 🟠.
-
Enhanced CPC was removed for Search/Display (Mar 2025); such campaigns now run as Manual CPC. Migration item 🟡.
W5 Keywords, match types & Quality Score
Data: keyword_view + ad_group_criterion (A7).
- Spend without conversions ≥ [R7] (lag-complete) → pause / lower bid / tighten match / check LP (X8). Below that, watch.
- Quality Score (1–10, keyword-level diagnostic, not an auction input). Use impression-weighted QS per campaign. Each component (expected CTR, ad relevance, landing page experience) is rated above / average / below average; a “below average” component points to the fix (copy vs LP vs grouping). Flag QS ≤ 3 with material spend.
- Match types: spend/conversions by type. Broad only with Smart Bidding and W1 = high. Without offline signals, prefer phrase + exact on core intent.
- Duplicates / internal competition: one owner per intent. Low CTR on high impressions → W7/W6.
- Structure: mixed-intent ad groups (30+ keywords) or no-volume single-keyword groups (rule of thumb: 5–20 themed keywords). Zero-impression / “low search volume” keywords 90+d → clean up 🟡.
- Brand isolation: brand keywords only in brand campaigns; brand terms negative elsewhere.
- Competitor keywords: own campaign and LP, trademark-policy compliant, higher CPA tolerance.
W6 Search terms & negatives
Data: search_term_view (A6); PMax/AI Max: campaign_search_term_view,
segments.search_term_match_source; negatives (A8: campaign_criterion, shared_criterion,
negative ad_group_criterion).
-
Tier every term with spend; report % of spend per tier. T3+T4 > [R8] = material leak 🟠/🔴.
Tier Meaning Action T1 High intent, matches offer (“opensearch consulting”) add as exact/phrase keyword if missing T2 Relevant, research stage (“opensearch vs elasticsearch performance”) keep if CPA OK; consider own ad group/offer T3 Tangential / ambiguous watch; negate at [R7] with 0 conversions T4 Irrelevant (jobs, free, tutorial, download, login, docs, wrong product) negate now -
Shared B2B negative list exists: jobs, careers, hiring, salary, resume, internship, course, certification, training, tutorial, “what is”, definition, pdf, examples, free, cheap, download, github, open source (unless the offer), login, documentation, install, error messages, student, “for beginners”.
-
Close-variant drift on exact/phrase. Routing: queries in the wrong ad group/campaign (brand in non-brand, Elasticsearch in OpenSearch) → routing negatives.
-
Positive candidates: converting or T1 terms not yet keywords. Never negate a term that converted (X5). Negatives: exact for one-offs, phrase for bad modifiers, no broad on single generic words.
-
Over-negation (quarterly): negatives blocking T1/T2 or active keywords (X6).
-
PMax / AI Max terms: same tiering. PMax takes up to 10,000 campaign-level negatives.
W7 Ads & assets
Data: ad_group_ad (A11), ad_group_ad_asset_view (A12), campaign_asset / customer_asset.
- Coverage: every enabled ad group has ≥1 approved, serving RSA (2–3 for testing) 🔴 if none.
- RSA depth: up to 15 headlines / 4 descriptions with distinct messages (offer, proof, differentiator, CTA, keyword). Ad strength is directional only 🟡.
- Asset performance: RSA assets have clicks/conversions/cost metrics (data from June 5, 2025); prefer them over performance labels, but treat per-asset ratios (CTR, CPA) as directional. Replace weak assets once they have [R9] volume.
- Pinning only for legally/brand-critical copy. Message match: top ad groups’ ads state their core intent.
- B2B qualifiers (“for enterprise teams”, “consulting, not training”) missing where W12 lead quality is poor 🟠.
- Assets: ≥4 sitelinks with descriptions, real-differentiator callouts, structured snippets, business name + logo; none stale 🟡.
- Auto-created / AI Max text customization assets: check accuracy and brand voice 🟠. High-spend copy unchanged 6+ months → test a new angle 🟡.
W8 Landing pages & post-click
Data: landing_page_view / expanded_landing_page_view (A13), HTTP fetch of every final URL
with spend, PageSpeed/CWV, GA4 if linked.
- Broken URLs 4xx/5xx 🔴; redirect chains or stripped GCLID 🟠.
- Weak pages: spend ≥ [R7] with 0 conversions, or CVR < half the account average.
- Message match keyword → ad → page; homepage for specific intents 🟠. Stale offers/claims 🟡.
- Core Web Vitals (mobile, 75th percentile): “good” = LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1. Failing on top-spend pages 🟠.
- Form: qualifying fields, works on mobile, conversion fires once (not on reload).
- URL-expansion destinations (AI Max / PMax): exclude careers, non-converting blog, login, docs, legal 🟠.
W9 Segments: geo, device, schedule, audiences, networks
Data: user_location_view / geographic_view, segments.device, day_of_week, hour,
ad_network_type (A14), audience views, user_list.
Compare CPA/CVR per segment vs campaign average; act only at [R10].
- Geo: spend without conversions; clicks outside the market (→ W2 location option).
- Device: mobile CPA ≫ desktop → mobile LP fix; under Smart Bidding only device −100% applies.
- Day/hour: sustained dead zones; ad schedule only if not on Smart Bidding or hours can’t be served.
- Networks: Search partners / Display CPA materially above Search → exclude (X17).
- Audiences: observation segments with clearly better/worse CVR; targeting-mode segments that shrink reach; customer and job-seeker exclusions; list sizes and Customer Match upload freshness (flat lists = broken tag/upload) 🟡.
- Demographics (A17): age, gender, household income, parental status. CPA/CVR and W12 SQL rate
per segment vs campaign average; exclude only at [R10].
- Unknown (
*_UNDETERMINED) is targeted by default. Google: exclude it only if you’re sure you want a narrow audience. Never exclude it on CPA alone; report its spend share. - Where: Search, Display, Video, Demand Gen support demographic targeting/exclusions (campaign or ad group). PMax: age and gender exclusions only, gender in beta. Household income: only some countries (Israel included).
- Smart Bidding ignores demographic bid adjustments: only exclusions matter (X14). Partial adjustments only matter on non-Smart strategies (e.g. Manual CPC).
- B2B (internal rule of thumb): decision-makers skew 25–54, but juniors research and refer. Exclude 18–24 / 65+ only with evidence (R10 + poor SQL rate, X21). Gender/parental/income exclusions need a documented reason.
- Proposable: campaign-level exclusion via
add_campaign_criterion(criterion_typeAGE_RANGE/GENDER/PARENTAL_STATUS/INCOME_RANGE,negative: true,demographic_code). Ad-group-level demographic criteria and bid modifiers are manual (manage_ad_group_criterionhandles keywords only). The API refuses excluding every value in a dimension.
- Unknown (
W10 Campaign-type fit, Performance Max, AI Max, Demand Gen & Display
Campaign-type fit (quarterly): does each goal run on the right type, and is a type missing or redundant? Uncertain direction (PMax vs Search, adding AI Max, Demand Gen test) → experiment (§8.6). Prerequisites and red flags are internal rules of thumb unless a source is cited.
| Goal / funnel role | Type | Prerequisites | Red flags |
|---|---|---|---|
| Capture own-brand demand | Search, brand-only | brand list; brand negatives elsewhere | brand inside non-brand/PMax (X10); IS < [R5] |
| Capture high-intent demand | Search non-brand, by service | W1 high; T1 keywords | mixed intents; Maximize Clicks |
| Competitor conquesting | Search, own campaign | own LP, trademark policy, higher CPA tolerance | mixed into non-brand |
| Expand query reach | AI Max for Search (setting on a Search campaign) | converting Search campaign, negatives, URL exclusions | judged < 4 weeks; brand leakage |
| Conversions across all inventory | PMax | [R1]-level volume, qualified/converted-lead goals + offline import (Google lead-gen best practice), assets, brand exclusions | form-fill-only goal → spam (X16) |
| Create demand / remarketing | Demand Gen (YouTube, Discover, Gmail, Maps, GDN) | video/image assets, Customer Match/remarketing lists | judged on last-click CPA vs Search |
| Awareness | Video (reach) | video assets, view-based success metric | used for lead CPA |
| Remarketing / reach (legacy) | Display | — | Display is moving into Demand Gen (Appendix B): plan the migration; no new Display builds |
| — | Shopping, App | N/A for B2B services | any spend |
Proposable: create_campaign makes SEARCH/DISPLAY/SHOPPING/VIDEO/PERFORMANCE_MAX campaigns, always
PAUSED. It has no Demand Gen option and no AI Max setting, and it needs an existing explicitly shared
budget (budgets are created with the non-allowlisted mutate). A PMax shell is useless without asset
groups (also mutate). In practice only a Search campaign on a shared budget is proposable. New
budgets, PMax/Demand Gen builds, AI Max toggles and Display→Demand Gen migration are manual (§8.4).
PMax:
- Channel split (channel performance report /
segments.ad_network_type): Display/video absorbing spend at far worse CPA = hidden cross-subsidy 🟠. - Brand exclusions when a brand Search campaign exists; otherwise PMax claims brand conversions and inflates results 🔴/🟠.
- Search terms & negatives: as W6. Search themes: up to 50 per asset group; match T1 intents.
- Asset groups: themed by persona/product, full creative, audience signals populated (Customer Match, converters, custom segments). Spend/conversions per group (A16).
- Final URL expansion and its exclusions. Placements: brand-safety scan; account-level placement exclusions.
- Lead gen: PMax on form fills without offline quality signals often buys spam → X16/W12.
AI Max for Search:
- Which features are on (search term matching, text customization, final URL expansion), each a deliberate choice. DSA campaigns are being upgraded to AI Max (Appendix B).
- AI Max-sourced queries (
segments.search_term_match_source) vs keyword-matched: CPA, tier mix, lead quality. Brand and URL controls set. - Judge after ≥4 weeks / one conversion cycle; review its search terms weekly in month one.
Demand Gen / Display / Video:
- Judge on funnel role, not last-click CPA vs Search: view-through, assisted conversions, Attributed branded searches (Demand Gen), conversion-lift studies.
- View-through window and whether view-throughs count in “Conversions”; audience layers per ad group, creative across aspect ratios, frequency caps, placement exclusions.
W11 Competition & market
Data: google_ads_get_auction_insights (IS, overlap, position-above, top-of-page,
outranking share per domain), CPC trend, google_ads_generate_keyword_ideas.
- New/growing competitors (IS or overlap ↑) → explains CPC inflation, lost IS (rank).
- Position-above ↑ on core campaigns → decide QS (W5) vs bid before raising bids.
- Competitors in the brand campaign’s auction → brand defense / trademark complaint.
- Our CPC ↑ with a stable competitor set → our QS or strategy, not the market.
- Impressions ↓ with stable IS = demand drop; IS ↓ = ours.
- Quarterly gaps: high-intent ideas with volume and no coverage.
- Auction-insights metrics can’t be combined with standard metrics in one query; join on campaign + dates.
W12 Business outcomes & lead quality
Data: CRM (HubSpot): leads with GCLID/UTM, lifecycle stage, SQL date, opportunity value, won.
- Lead → SQL rate by campaign (keyword/term when volume allows); below [R11] = junk source 🟠/🔴.
- Cost per SQL / opportunity, pipeline ROAS per campaign — use these, not CPA, for budget.
- Spam/bot leads concentrated in PMax/Display/partners → exclusions, form hardening.
- Offline values track expected value (MQL ≪ SQL ≪ Opp ≪ Won). Pipeline matures over 30–180d: don’t kill new campaigns before 60–90d unless lead quality is clearly bad.
No CRM data → say so; all efficiency findings become “platform-conversion based”, lower confidence.
6. Finding format
- id: W6-03
workstream: W6
title: "Job-seeker queries consume 12% of OpenSearch Consulting spend"
entities: # ids from the context-pack entity map
campaign_ids: ["2134..."]
ad_group_ids: []
criteria: ["opensearch jobs", "opensearch developer salary"]
evidence: # reproducible
window: "2026-08-01..2026-08-31"
metrics: {cost_ils: 1840, clicks: 61, conversions: 0, share_of_campaign_spend: 0.12}
query: "A6"
severity: material # critical | material | hygiene
confidence: high # high | medium | low (capped by W1 verdict, §7.1)
est_monthly_impact_ils: 1800 # savings or incremental value, signed
root_cause_hint: "No shared job/career negative list attached"
candidate_actions:
- kind: proposable # proposable (§8.3 table) | manual (§8.4)
tool: add_campaign_criterion
summary: "Add phrase negative 'jobs' to campaign 2134..."
depends_on: [] # e.g. ["W1"]
conflicts_with: [] # filled in Phase 2
No finding without evidence.metrics. Always estimate impact (savings = waste removed; scale =
conversions × value). confidence: low when below §9.1 thresholds.
7. Phase 2: Consolidation & cross-checks
7.1 Gate on W1
| W1 verdict | Effect |
|---|---|
high | findings stand |
medium | CPA/ROAS-based findings drop one confidence level; volume/hygiene findings unaffected |
low | suspend all bid/budget/target/pause proposals. Report only W1 fixes, conversion-independent waste (T4 terms, broken URLs, disapprovals, settings) and a data exclusion for the broken period |
7.2 Cross-check matrix
Each rule merges findings into a root cause, resolves a conflict, or blocks an action.
| # | If… | …and… | Then |
|---|---|---|---|
| X1 | W3 limited by budget | CPA > target or poor SQL rate | Block budget ↑. Root cause = efficiency → W5/W6/W8 |
| X2 | W3 limited by budget | CPA ≤ target, W12 OK | Scale: budget +[R4], after marginal-CPA check |
| X3 | W3 lost IS (rank) | W5 low QS / below-avg expected CTR | Relevance: copy (W7) / grouping before bids |
| X4 | W3 lost IS (rank) | W11 competitor position-above ↑ | Bid/target change only with CPA headroom |
| X5 | W6 negative candidate | term/variant converted in context window | Drop the negative |
| X6 | W6 new negative | an active keyword contains/equals it | Conflict: rescope to exact or ad-group level |
| X7 | W6 positive candidate | same/close keyword exists elsewhere | Don’t duplicate; routing negatives or move |
| X8 | W5 spend w/o conversions | W8 page broken/slow/poor overall | Fix LP before pausing keywords |
| X9 | high CTR, low CVR | W6 queries T1 | LP/offer problem (W8); if T3/T4 → query problem (W6) |
| X10 | W10 PMax strong conversions | brand IS fell / no brand exclusions | Cannibalization: brand exclusions first, re-judge |
| X11 | any inflection on date D | W2 changes near D | Attribute to the change; revert/adjust candidate |
| X12 | any bid/budget/target proposal | campaign learning, or spend change < 1 conversion cycle ago | Defer (note in report) |
| X13 | W4 target change | budget-limited after Aug 17, 2026 | Target = real breakeven; consider no target |
| X14 | W9 segment adjustment | Smart Bidding | Only device −100%, location exclusion, schedule removal apply; drop partial adjustments |
| X15 | Google recommendation / auto-apply | own finding says opposite | Own finding wins; dismiss_recommendation |
| X16 | strong platform CPA | poor SQL rate | Rank by cost per SQL; feed offline values (W1) |
| X17 | W9 partners/Display worse | W2 setting on | Merge into one settings finding |
| X18 | budget increases | their sum | Fit the monthly envelope or fund by cuts; else rank and cut |
| X19 | several findings on one campaign | — | ≤1 spend-affecting change per campaign per cycle (negatives and assets don’t count) |
| X20 | Phase 4: last change didn’t deliver | — | Revert/adjust before any new proposal on that entity |
| X21 | W9 demographic exclusion | W12 SQL rate for the segment OK, or segment is Unknown | Block; exclude only when platform CPA and lead quality both fail |
7.3 Dedupe, merge, sanity
- Same entity + action from two workstreams → one finding with both evidences. Many leaves with one cause (20 job queries) → one finding listing the items.
- Workstream spend totals ≈ account spend for the window (±2%); otherwise someone used the wrong window or filter.
- Every action references existing, ENABLED ids. Estimated savings ≤ the entity’s spend.
- Nothing contradicts a recorded human decision.
8. Phase 3: Prioritization, proposals & tasks
8.1 Score and bucket
priority = est_monthly_impact × confidence (high 1.0 / medium 0.6 / low 0.3) ÷ effort (1 = one
proposable call, 2 = copy/LP work, 3 = restructure or tracking project). Critical items jump the
queue.
- Fix now: tracking, broken LPs, disapprovals, runaway spend, brand exposure.
- Quick wins (this cycle, proposable): negatives, pausing clear losers, demographic exclusions, asset swaps, one budget/target step on qualified campaigns. Network/setting fixes are quick but manual (§8.4).
- Optimizations (2–4 weeks): new RSA angles, restructuring, LP tests, audiences, strategy migrations.
- Strategic (quarter): offline/value bidding, brand incrementality test, PMax/AI Max adoption or rollback, gap campaigns, cross-channel reallocation.
8.2 How changes are proposed and applied
propose_google_ads_change(tool_name, arguments, customer_id, summary, expected_impact, rationale):
- One call = one change, highest priority first, capped (weekly ≤10).
summary= one-line title;expected_impact(required) quantified from the finding;rationale= evidence + cross-checks passed. - Creates a
paid-ads/todotask labelledgoogle-ads-change+Google Ads,requires_human=true, assigned to the Ad Change Applier; spend-affecting and destructive changes get high priority. argumentsuse the tool’s own names and real ids from pulled data. Errors list the expected names — fix or drop.partial_failure/validate_onlyare refused; an identical open proposal returnsduplicate.create_*entities are forced PAUSED; enabling is a separate proposal.- A human approves by clearing Requires human; the deterministic applier makes that exact call once and writes an apply ledger. Edited proposals fail their seal — re-propose instead. Review LLMs have no ad write tools.
8.3 Proposable changes (allowlist — anything else is refused)
| Finding | tool_name | Notes |
|---|---|---|
| Campaign negative keyword (or other campaign criterion) | add_campaign_criterion | exact for one-offs, phrase for modifiers |
| Demographic exclusion (campaign level) | add_campaign_criterion | negative: true + demographic_code; X21 |
| Remove harmful negative / criterion | remove_campaign_criterion | over-negation audit |
| Add/pause keyword, change bid | manage_ad_group_criterion | spend-affecting |
| Budget step | update_campaign_budget | [R4]; spend-affecting |
| Campaign status, name, start/end date, target CPA / target ROAS | update_campaign | only these fields (plus the EU political-ads flag); one change per proposal; spend-affecting |
| New RSA / variant | create_ad | PAUSED |
| Pause / edit ad | update_ad | |
| Routing ad group / restructure | create_ad_group (PAUSED), update_ad_group | update_ad_group spend-affecting |
| Google recommendation | apply_recommendation (spend-affecting), dismiss_recommendation | |
| New campaign (keyword gap) | create_campaign | PAUSED; spend-affecting; needs an existing shared budget (W10) |
8.4 Manual tasks (not proposable)
Shared negative lists, conversion actions/goals, campaign settings update_campaign can’t set
(networks/Search partners, location option, languages, ad rotation, bid-strategy switches, URL
suffix/template), account settings (auto-apply, auto-tagging, brand lists), data exclusions, experiments, Merchant Center, landing-page work, CRM/offline import, policy
appeals, access. Create one Paid Ads task per action via the agent’s tasks output (board
paid-ads, requires_human: true, source_task_id is stamped automatically) or
create_board_task(board_id="paid-ads", ...). Title = the action; description = finding id,
evidence pointer and expected impact. Don’t copy the report’s prose. If neither is available,
list them as manual in the report’s ranked actions.
8.5 Phase 4: Verify last cycle (run first)
list_board_tasks("paid-ads", "done")(andtodo) →google-ads-changetasks since the last review;get_board_taskfor fields.- Classify: applied (
applied_at), failed/held (apply_error, orapply_started_atwithoutapplied_at= unknown outcome, needs a human), pending approval (requires_human), rejected (cancelled/archived). - For applied changes, compare
expected_impactwith a lag-complete before/after window on the entity inapplied_resources. Verdict: delivered / partial / no effect / harmful. - Harmful or no effect → revert/adjust candidate (X20). Stale pending proposals → mention in the report, don’t re-propose (duplicates are refused anyway).
8.6 Experiments over guesses
For uncertain-direction changes (strategy switch, broad match/AI Max, PMax vs Search, brand bidding on/off) recommend a Google Ads experiment (50/50, ≥4 weeks or one conversion cycle, success metric agreed upfront, ideally cost per SQL) or a geo holdout — as a manual task, not a direct change.
9. Thresholds & decision rules
9.1 Thresholds (stated once; internal rules of thumb unless marked Google)
| Id | Rule | Value |
|---|---|---|
| R1 | Conversions per campaign/portfolio per 30d for tCPA/tROAS | ≥ 15 minimum, 30–50 comfortable |
| R2 | Platform vs CRM lead gap | > 20% → investigate |
| R3 | Projected month spend vs 30.4 × daily budget (cap: Google) | outside 80–110% |
| R4 | Budget/target change per step | ≤ 20%; one step per conversion cycle |
| R5 | Brand search IS | ≥ 90% |
| R6 | Target vs actual CPA/ROAS gap | > ±30% |
| R7 | Spend with 0 conversions before pausing/negating (keyword, term, LP) | ≥ 1.5–2× target CPA (or clicks ≥ 2–3 × 1/CVR), lag-complete, ≥ 30 days |
| R8 | T3+T4 share of search-term spend | > 15–20% |
| R9 | Asset volume before replacing | ~5,000 impressions; “learning” ≥ 2 weeks |
| R10 | Segment exclusion (device/geo/hour) | segment spend ≥ 2× target CPA and CPA > 2× campaign avg, two consecutive periods |
| R11 | Lead → SQL rate (B2B paid) | ≥ ~20% |
| — | Judge new campaign / strategy / AI Max | ≥ 4 weeks and ≥ 1 conversion cycle (B2B pipeline 60–90d) |
| — | Core Web Vitals “good” (Google, web.dev) | LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1 |
Benchmarks: use the account’s own trailing history; third-party industry averages are not reliable for niche B2B in ILS.
9.2 Pause vs optimize (stop at the first match)
- At/under target → keep; scale if lost IS (budget) (X2).
- Below R7 data → watch; fix only obvious relevance gaps.
- W1 not healthy → don’t pause on CPA; fix tracking.
- LP broken/mismatched (W8) → fix LP first.
- Queries mostly T3/T4 (W6) → negatives / tighten match; re-evaluate.
- QS ≤ 3 with a below-average component → copy/grouping fix; re-evaluate.
- Otherwise → lower bid/target; pause if still failing next cycle.
9.3 Other triggers
- Scale: X2 holds, lost IS (budget) ≥ 10–20%, marginal CPA ≤ max allowable, step R4.
- Change bid strategy: volume crossed a W4 rung for 2+ months, target chronically unreachable, or offline values arrived. Prefer an experiment.
- Refresh creative: weak assets at R9, expected CTR below average on top keywords, CTR −20% at stable IS, or copy 6+ months old.
- Restructure: mixed-intent ad groups, brand + non-brand in one campaign, or fragmentation keeps everything below R1.
10. Symptom → root cause
| Symptom | Check first | Likely causes |
|---|---|---|
| Conversions dropped suddenly | W1 recency, W2 history, site deploys | broken tag, form/consent change, action edited, LP down, upload stalled |
| Conversions doubled, CRM flat | W1 duplicates | double-fire, GA4 import + tag both primary, spam |
| CPC rising | W11, W5 QS trend, W4 targets | new competitor, quality decay, target loosened, broad expansion |
| Impressions ↓, IS ↓ | W3 lost budget/rank, W2 policy | budget cut, rank loss, disapprovals, limited ad serving |
| High CTR, low CVR | W6 tiers, W8 | LP/offer mismatch, info-intent queries, mobile form broken |
| Good CPA, poor lead quality | W12, W10, W9 partners | spam, network leakage, student queries, no offline signals |
| CPA drifted up after Aug 17, 2026 | W3 + W4 budget-limited list | bid-to-target change; target above real breakeven |
| Brand conversions ↓ | W3 brand IS, W11 brand auction, W10 brand exclusions | competitor on brand, PMax overlap, brand budget cap |
11. Report format
One markdown asset per review, Google Ads Review: <customer_id> <period>. Data, then
interpretation, then actions:
- Verdict (healthy / needs attention / critical) + W1 measurement confidence.
- Scorecard vs previous period and target: spend, conversions, CPA/ROAS, cost per SQL, IS; windows stated.
- Last cycle follow-up (§8.5). 4. Critical issues with evidence and action.
- Ranked actions: finding id, action, entity, expected impact, confidence, proposable/manual, task id.
- Workstream notes, including “checked and fine” (shows coverage).
- Deferred / blocked (X1, X12, W1 gate…) with reason and revisit date. 8. Open questions for a human.
Appendix A: GAQL cookbook
Prefer the typed tools (get_campaigns, get_search_terms, …) where one fits. Otherwise run with google_ads_search; the server runs API v23. If a query fails, check the resource with
google_ads_describe_gaql_resource instead of guessing. Costs are micros. For lag-complete windows
replace DURING LAST_30_DAYS with segments.date BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'.
M is shorthand: expand it to metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions before running.
-- A1 Account (W1/W2)
SELECT customer.id, customer.currency_code, customer.time_zone, customer.auto_tagging_enabled,
customer.optimization_score, customer.conversion_tracking_setting.conversion_tracking_status FROM customer
-- A2 Conversion actions (W1)
SELECT conversion_action.id, conversion_action.name, conversion_action.type, conversion_action.category,
conversion_action.primary_for_goal, conversion_action.counting_type,
conversion_action.click_through_lookback_window_days, conversion_action.attribution_model_settings.attribution_model,
conversion_action.value_settings.default_value, conversion_action.value_settings.always_use_default_value
FROM conversion_action WHERE conversion_action.status = 'ENABLED'
-- A3 Conversions per action per day (W1)
SELECT segments.date, segments.conversion_action_name, metrics.all_conversions, metrics.conversions
FROM customer WHERE segments.date DURING LAST_30_DAYS
-- A4 Conversion lag (Phase 0)
SELECT segments.conversion_lag_bucket, metrics.conversions FROM campaign WHERE segments.date DURING LAST_90_DAYS
-- A5 Campaigns, IS, status (W3/W4)
SELECT campaign.id, campaign.name, campaign.advertising_channel_type, campaign.bidding_strategy_type,
campaign.primary_status, campaign.primary_status_reasons, campaign.bidding_strategy_system_status,
campaign_budget.amount_micros, campaign.target_cpa.target_cpa_micros, campaign.maximize_conversions.target_cpa_micros,
campaign.target_roas.target_roas, campaign.maximize_conversion_value.target_roas, M, metrics.conversions_value,
metrics.search_impression_share, metrics.search_budget_lost_impression_share,
metrics.search_rank_lost_impression_share, metrics.search_top_impression_share,
metrics.search_absolute_top_impression_share
FROM campaign WHERE campaign.status = 'ENABLED' AND segments.date DURING LAST_30_DAYS
-- A6 Search terms (W6); PMax: FROM campaign_search_term_view (campaign_search_term_view.search_term)
SELECT campaign.id, ad_group.id, search_term_view.search_term, search_term_view.status,
segments.search_term_match_type, segments.search_term_match_source, M
FROM search_term_view WHERE segments.date DURING LAST_30_DAYS AND metrics.impressions > 0
ORDER BY metrics.cost_micros DESC
-- A7 Keywords + QS (W5). search_predicted_ctr = expected CTR, creative_quality_score = ad relevance,
-- post_click_quality_score = landing page experience
SELECT ad_group.id, ad_group_criterion.criterion_id, ad_group_criterion.keyword.text,
ad_group_criterion.keyword.match_type, ad_group_criterion.quality_info.quality_score,
ad_group_criterion.quality_info.search_predicted_ctr, ad_group_criterion.quality_info.creative_quality_score,
ad_group_criterion.quality_info.post_click_quality_score, M
FROM keyword_view WHERE ad_group_criterion.status = 'ENABLED' AND segments.date DURING LAST_30_DAYS
-- A8 Negatives (W6): campaign level, then shared lists
SELECT campaign.id, campaign_criterion.keyword.text, campaign_criterion.keyword.match_type
FROM campaign_criterion WHERE campaign_criterion.negative = TRUE AND campaign_criterion.type = 'KEYWORD'
SELECT shared_set.id, shared_set.name, shared_criterion.keyword.text, shared_criterion.keyword.match_type
FROM shared_criterion WHERE shared_set.type = 'NEGATIVE_KEYWORDS'
-- A9 Networks, location option, tracking (W2)
SELECT campaign.id, campaign.network_settings.target_search_network,
campaign.network_settings.target_partner_search_network, campaign.network_settings.target_content_network,
campaign.geo_target_type_setting.positive_geo_target_type, campaign.final_url_suffix, campaign.tracking_url_template
FROM campaign WHERE campaign.status = 'ENABLED'
-- A10 Change history (W2): date filter within last 30 days and LIMIT (max 10,000) are required
SELECT change_event.change_date_time, change_event.change_resource_type, change_event.resource_change_operation,
change_event.client_type, change_event.user_email, change_event.changed_fields, campaign.name
FROM change_event WHERE change_event.change_date_time DURING LAST_14_DAYS
ORDER BY change_event.change_date_time DESC LIMIT 1000
-- A11 Ads, strength, policy (W7)
SELECT ad_group.id, ad_group_ad.ad.id, ad_group_ad.status, ad_group_ad.ad.type, ad_group_ad.ad_strength,
ad_group_ad.policy_summary.approval_status, ad_group_ad.policy_summary.review_status, ad_group_ad.ad.final_urls, M
FROM ad_group_ad WHERE ad_group_ad.status != 'REMOVED' AND segments.date DURING LAST_30_DAYS
-- A12 RSA assets (W7)
SELECT ad_group_ad.ad.id, asset.text_asset.text, ad_group_ad_asset_view.field_type,
ad_group_ad_asset_view.performance_label, ad_group_ad_asset_view.pinned_field, M
FROM ad_group_ad_asset_view WHERE segments.date DURING LAST_30_DAYS
-- A13 Landing pages (W8)
SELECT landing_page_view.unexpanded_final_url, M, metrics.mobile_friendly_clicks_percentage, metrics.speed_score
FROM landing_page_view WHERE segments.date DURING LAST_30_DAYS
-- A14 Segments (W9): swap the segments for segments.day_of_week, segments.hour
SELECT campaign.id, segments.ad_network_type, segments.device, M FROM campaign WHERE segments.date DURING LAST_30_DAYS
SELECT campaign.id, user_location_view.country_criterion_id, user_location_view.targeting_location, M
FROM user_location_view WHERE segments.date DURING LAST_30_DAYS
-- A15 Recommendations (W2)
SELECT recommendation.resource_name, recommendation.type, recommendation.campaign, recommendation.dismissed,
recommendation.impact.base_metrics.conversions, recommendation.impact.potential_metrics.conversions
FROM recommendation
-- A16 PMax asset groups (W10)
SELECT campaign.id, asset_group.id, asset_group.name, asset_group.status, asset_group.ad_strength,
metrics.cost_micros, metrics.conversions, metrics.conversions_value
FROM asset_group WHERE segments.date DURING LAST_30_DAYS
-- A17 Demographics (W9): swap to gender_view/gender.type, income_range_view/income_range.type,
-- parental_status_view/parental_status.type. Ad-group based: PMax is not covered
SELECT campaign.id, ad_group.id, ad_group_criterion.age_range.type, ad_group_criterion.negative, M
FROM age_range_view WHERE segments.date DURING LAST_30_DAYS
Auction insights (W11): google_ads_get_auction_insights per campaign and date range.
Appendix B: Platform changes that affect audits
| When | Change | Audit implication |
|---|---|---|
| Mar 2025 | Enhanced CPC removed for Search/Display (Shopping since Oct 2023); runs as Manual CPC | migrate strategy (W4) |
| Jun 2025 → | RSA asset-level clicks, conversions, cost (data from June 5, 2025) | prefer over performance labels (W7) |
| 2025–2026 | PMax: search terms report, channel performance, up to 10,000 campaign negatives, 50 search themes per asset group, brand exclusions | treat PMax like Search in W6; check cross-subsidy and cannibalization (W10) |
| 2025–2026 | AI Max for Search (search term matching, text customization, final URL expansion) | audit each feature, URL exclusions, {lpurl} templates, AI copy, query quality (W10) |
| Jan 2026 | Attributed branded searches metric for Demand Gen | upper-funnel value beyond last click (W10) |
| Jun 2026 | Limited ad serving expanded on Search; Aug 2026 to all Google Ads; gradual through 2028 | check notices, own-brand clarity (W2) |
| Jun 15, 2026 | Google Ads API click-conversion uploads fail for tokens with no prior upload history; Data Manager API is the path for new integrations | verify offline imports land (W1, W12) |
| Aug 17–27, 2026 | Budget-limited tCPA/tROAS (Search, Shopping, PMax, Demand Gen, Travel) now bid to target instead of beating it | re-baseline targets; wait 1–2 conversion cycles; before/after (W3/W4) |
| Jun 2026 → | Display campaigns move to Demand Gen (GDN on Demand Gen): migration tool from Jun 2026; later, new Display campaigns only in Demand Gen and remaining ones auto-migrated (no dates yet); no revert | plan migration as a manual task (W10) |
| Sep 2026 → Feb 2027 | Eligible Search campaigns with legacy settings auto-upgrade to AI Max from Sep 2026; DSA upgrade extended to Feb 2027 | check which AI Max features were switched on (W10) |