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.

AI Marketing Agent: A Shadow-Week Reconciliation Field Note cover illustration

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

83.50 of synthetic ad spend, 42 ad clicks, 37 analytics sessions, six primary conversions, and seven secondary conversions in separate fields. It also surfaced three problems: one analytics row without a campaign ID, one renamed campaign, and one search term present in two ad groups. That is the sort of dull honesty I want before an agent earns a write path.

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 surfaceWhat the packet retainedWhat it refused to infer
Google Ads-shaped rowsSpend micros, impressions, clicks, campaign and ad-group IDsThat an identical search-term string means an identical row
Conversion rowsSix primary and seven secondary conversionsThat every conversion action belongs in one optimization total
Analytics-shaped rows37 sessions and six key eventsThat sessions are ad clicks under another name
Exception ledgerMissing ID, name drift, repeated term across ad groupsThat 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.

Two differently colored reporting streams entering a controlled reconciliation table without losing their source identity
Reconciliation should preserve the boundary between advertising and analytics records before it produces a shared decision packet.

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

83.50 once. A negative value or fractional micro blocks the packet. Converting during ingestion and again during presentation would quietly turn the same spend into $0.0001835. Keeping the raw unit beside the display value makes that error visible.

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.

Local policy: the fixture blocks any mismatch. That is not a limitation of the Google APIs; it is a conservative rule for this shadow report.

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.

A marketing operator receiving a compact evidence folder after automated records pass through a review lens
The output of a shadow week is a review packet, not an automatic campaign change.

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