2026-07-17
AI Marketing Agent: A Shadow-Week Reconciliation Field Note
A synthetic shadow week shows how an AI marketing agent should preserve units, source metrics, campaign IDs, conversion roles, and unresolved exceptions before it earns write access.
The first useful version of an AI marketing agent should not change a bid, publish an ad, or send a campaign. It should explain why a weekly number is safe to quote. That sounds timid until one reporting row is in micros, another source uses sessions instead of clicks, a campaign name changes midweek, and the same search term appears under two ad groups.
I gave a small local compiler a deliberately messy synthetic week and asked for one acquisition memo. The fixture contained Google Ads-shaped metric and conversion rows plus Google Analytics-shaped session rows. It spent no real money and touched no account. Its job was narrower: retain source meaning, expose unresolved joins, and refuse a polished recommendation when the evidence contract was broken.
The result was READY_WITH_EXCEPTIONS, not a clean green light. All 24 expected decisions matched. The packet kept
The first useful run has no write path
Marketing software makes action look like progress. An agent that can change a budget produces an impressive demo; an agent that says “this report is incomplete” looks less exciting. In a real operating loop, the second behavior is harder to build and easier to trust.
The shadow-week contract therefore sets externalWritesAllowed to false. The agent may assemble a packet, but it cannot apply a recommendation. If a prompt asks it to pause a campaign or upload a conversion, admission stops before interpretation. Approval is not another sentence in the prompt. It is a separate transition after a person can inspect scope, freshness, metrics, exceptions, and cited evidence.
A marketing agent earns authority by preserving inconvenient facts, not by producing a decisive tone.
This boundary also makes evaluation cleaner. If the agent cannot mutate anything, a bad draft remains a bad draft rather than becoming a budget incident. The team can compare packets for a few weeks, learn which exceptions recur, and tighten the contract without reverse-engineering live changes.
The dataset was deliberately small and ugly
The retained fixture covers July 6–12, 2026 in America/New_York and USD. Those values are test inputs, not a recommended calendar or timezone. Five ad metric rows include a zero-impression campaign and the phrase “ai agent platform” in two different ad groups. Four conversion rows split one declared primary action from a newsletter action. Four analytics rows include a renamed campaign and an unjoinable legacy-newsletter row.
| Evidence surface | What the packet retained | What it refused to infer |
|---|---|---|
| Google Ads-shaped rows | Spend micros, impressions, clicks, campaign and ad-group IDs | That an identical search-term string means an identical row |
| Conversion rows | Six primary and seven secondary conversions | That every conversion action belongs in one optimization total |
| Analytics-shaped rows | 37 sessions and six key events | That sessions are ad clicks under another name |
| Exception ledger | Missing ID, name drift, repeated term across ad groups | That a neat total is more important than coverage |
Google’s current Ads reporting documentation explains why this care matters. A report is implicitly segmented by the resource in its FROM clause. Adding segments splits metric values across combinations and can multiply rows. The search-term view is also segmented by ad group, so matching text can legitimately appear more than once. “Deduplicate the CSV” is not a reporting policy.
83.50 is not a fact until the unit survives
The five synthetic metric rows sum to 183500000 cost micros. The compiler stores that integer and renders
Currency and timezone are equally structural. The fixture rejects an Ads source in EUR, an Analytics source in GBP, and either source in UTC because the contract declares USD and New York time. This is intentionally strict. A more capable deployment could normalize currencies and timezones, but only if it records the rate, boundary, and provenance. Silent normalization is worse than no normalization.
Troubleshooting row identity: one phrase can be two real rows
The string “ai agent platform” appears twice on the same date. One row belongs to ag-11; the other belongs to ag-12. The compiler keys a metric row by date, campaign ID, ad-group ID, and search-term value. The two rows survive and the repeated phrase becomes an exception worth reviewing. An exact duplicate of that composite key blocks the packet.
This distinction protects both undercounting and overcounting. Dropping one legitimate row loses spend and clicks. Keeping an accidentally repeated page without a composite identity double-counts them. The probe exercises incomplete pagination separately: if Ads reports one of two expected pages, or Analytics reports zero of one, compilation stops. Row identity cannot repair missing pages.
Google’s segmentation guide warns that additional segment fields can expand row counts, that fields must be compatible, and that some segment selections implicitly filter results. A query using keyword segments can exclude traffic that is not associated with Search keyword criteria. The agent should retain the query shape with the packet. Otherwise the reader cannot tell whether “zero” means no activity or an excluded slice.
Campaign names are labels; IDs carry the join
The synthetic Ads rows call campaign c-101 “Search Core.” One analytics row calls the same ID “Search Core — renamed.” Joining on the label would split one campaign into two. Joining on the ID keeps the records related and writes campaign_name_drift:c-101 to the exception ledger.
A fourth analytics row has the label “Legacy newsletter” and no campaign ID. The compiler does not guess. It keeps four sessions and one key event in the analytics total, marks the row unjoined, and prevents campaign-level efficiency claims from using it. That makes the top-line source total complete while keeping the cross-source comparison honest.
The Analytics Data API returns tables built from requested dimensions and metrics for a specified property and date range. Its schema distinguishes identifiers, names, dates, sessions, key events, and many other concepts. The safest join is the one the sources actually support, not the one that happens to produce a filled spreadsheet.
Keep clicks and sessions in separate columns
The fixture has 42 ad clicks and 37 analytics sessions. The five-count difference is not treated as data loss, attribution truth, or evidence that either system is wrong. These are different measures from different reporting systems. The packet names both, names their source, and declines to manufacture a blended “visit” metric.
This is a useful test for prose generation. A language model is rewarded for smoothing a story. Reporting work often needs the opposite: two adjacent numbers and a sentence explaining why they have not been reconciled. If a weekly memo silently changes “clicks” to “sessions” because the terms feel close, the agent has already failed even when the arithmetic is perfect.
The same rule applies to conversion actions. The local contract declares lead-start as primary, yielding six conversions. Seven newsletter conversions remain visible as secondary. Google Ads supports conversion actions and conversion goals precisely because actions have different roles. The agent should not choose that role from the action’s friendly name.
An exception ledger beats a clean lie
The baseline packet carries three exceptions. None blocks the source totals; all three block a claim that campaign-level performance is completely reconciled. This middle state is important. A binary success/failure field tends to either bury usable evidence or waive away gaps.
{
"status": "READY_WITH_EXCEPTIONS",
"adsMetrics": {
"spendMicros": 183500000,
"spend": 183.5,
"clicks": 42,
"primaryConversions": 6,
"secondaryConversions": 7
},
"analyticsMetrics": { "sessions": 37, "keyEvents": 6 },
"exceptions": [
"unjoined_analytics_rows:1",
"campaign_name_drift:c-101",
"same_term_multiple_ad_groups:ai agent platform"
]
}
A recommendation may cite an exception and ask for a tag review. It may not invent a causal diagnosis. The test case recommendation-without-source blocks compilation even though the proposed sentence could sound perfectly reasonable.
Twelve bad packets stopped before any prose
The fixture SHA-256 is e1b7653ddf1d0843cf3ac71c648119f9b5be6560972c88a08bd22834a1f92ca5. All 24 expected decisions matched. Ten mutations blocked timezone, currency, pagination, open-window, cost-unit, row-identity, or external-write violations. Two more blocked missing packet sections, and one blocked an unsupported recommendation. Positive cases preserved zero rows, distinct ad-group rows, source-specific totals, campaign-name drift, primary versus secondary actions, and micros conversion.
No Google Ads or Analytics account was queried. No OAuth token, customer ID, property ID, user record, search term, or campaign from a real business entered the fixture. No model was called. The probe did not measure attribution accuracy, recommendation quality, API latency, quota behavior, freshness delay, cost, incrementality, revenue, or production reliability.
That boundary is not a footnote. It is what makes the evidence useful. The probe shows that a deterministic reporting shell can reject known structural mistakes before a model writes a memo. It does not show that the memo will be insightful.
What belongs in the Monday packet
A reviewable packet needs six sections: scope, freshness, Ads metrics, analytics metrics, exceptions, and recommendations. Scope includes the exact inclusive dates, timezone, and currency. Freshness includes page counts and whether the reporting window is closed. Each metric table retains source vocabulary. Every recommendation points to a row, exception, or declared control.
I would also keep the query or request shape, source account identifiers in a protected audit record, a body hash for the generated memo, and the prior packet for comparison. Those additions were not executed by this fixture, so they are design recommendations rather than test results.
One practical review question catches a surprising number of problems: What would have to be false for this recommendation to change? If the answer is a hidden join, an undeclared conversion action, a missing page, or a still-open day, the packet is not ready for action.
A write permission comes after the reporting contract
After several stable shadow weeks, the next capability should still be narrow. Creating a draft negative-keyword list is safer than applying it. Preparing a budget-change manifest is safer than editing budgets. A human can review the exact customer, campaign, term, match type, old value, new value, evidence window, and rollback before a separate executor receives authority.
Do not promote the same agent merely because its prose improved. Promotion should depend on measured packet completeness, exception handling, replayability, and the cost of a false action. Keep reporting and mutation identities separate so a compromised reader cannot quietly become a writer.
The mature design is less magical than the demo: deterministic ingestion, explicit exceptions, a model used where language helps, and a consequential action behind another gate. That is enough for an AI marketing agent to be useful without pretending that a weekly narrative is the same thing as operational control.
Primary sources
- Google Ads API reporting overview — resources, queries, metrics, and reporting surfaces.
- Google Ads API segmentation — implicit resource segmentation, row multiplication, compatibility, filtering, and search-term view identity.
- Google Ads API conversion management — conversion actions, goals, reporting, monitoring, and imports.
- Google Analytics Data API basics — property, date range, dimensions, metrics, filters, and pagination.
- Google Analytics Data API dimensions and metrics — the current reporting vocabulary used to keep identifiers and measures explicit.