An LLM auditing a Google Ads account had a habit that looked, on paper, like a working system: it produced confident, well-reasoned recommendations every week. The problem was that several of them were already done.
One week it recommended disabling the Display Network expansion on a Search campaign — a campaign where that setting had been disabled eight days earlier. Another week it recommended adding audience signals to a Performance Max asset group that already carried seven of them. Each recommendation was internally sound. Each was reasoning from historical spend, against settings that no longer existed.
This matters because of what the report is for. The weekly Google Ads health check is part of a digital operations stack run for three B&B operators managing five properties — small businesses in Hengchun that could never have afforded a dedicated ads analyst, a data engineer, and a weekly reporting cadence as separate hires. The whole thing only works because an AI does the analyst’s reading and a human does the judgement. But a report that tells the operator to undo work they already did, every week, erodes the one thing the system is selling: that you can trust it without checking it yourself.
A correct conclusion from stale inputs is still wrong
The model wasn’t hallucinating. It was doing exactly what the prompt asked: read 30 days of performance data, find the leaks, recommend fixes. The trap is that performance data is a trailing record. If a campaign burned budget on the Display Network for the first three weeks of the window and the operator switched it off in week four, the 30-day numbers still show the waste. A reasoner that only sees spend will correctly diagnose a problem that has already been solved.
The naive fix is to add a sentence to the prompt: “check whether the setting is already off.” That doesn’t work, because the model had no field to check. The fetch layer pulled spend, conversions, and CTR — the outcome metrics — but not the current configuration values that produced them. You can’t instruct a model to verify against data it was never given.
The fix lives in the fetch layer, not the prompt
So the change was to fetch the current state alongside the outcomes. For the Display Network case, that meant pulling campaign.network_settings.target_content_network — the live boolean for whether the campaign is still spending on Display at all. For the audience signals case, it meant counting the actual audiences attached to each Performance Max asset group, so the model knows there are seven before it suggests adding a fourth.
Then the prompt was rewritten to make these values a gate, not a hint. The Display rule now opens with a hard pre-check: if targetContentNetwork is false, skip the entire finding — do not recommend turning off something that is already off; the historical spend was burned before the setting changed. Only if it is still true does the model proceed to the spend analysis it used to jump straight into. The audience-signal rule is tiered the same way: four or more signals means “already sufficient, don’t recommend adding”; one to three means recommend; zero is top priority.
The ordering is the whole point. The model reads the current setting first and the historical outcome second. A competent prompt engineer would reach for “tell the model to be more careful.” The actual lever is structural: the model can only be careful about state it can see, and it will only check it first if the check is the first instruction.
The same trap, three more times
Once you see this failure mode, it appears everywhere a reasoner meets trailing data. A daily-spend spike that looks like a billing anomaly is, on a campaign’s launch day, just the structural certainty of a campaign starting. A flat-rate monthly projection extrapolated from a five-day sample reads as a stable forecast when the sample includes a budget change that hasn’t settled. Each got the same treatment: a mandatory check of recentChanges — the account’s own change history — before the model is allowed to call something an anomaly. If the spike day coincides with a CAMPAIGN CREATE event, it isn’t an anomaly; it’s a first day. If a monthly projection is built on a sample shorter than two weeks or spanning a budget reset, the model must label it as provisional rather than present it as an expected value.
None of these are clever prompt tricks. They are the same insight applied repeatedly: a reasoner extrapolating from history will misattribute cause and effect unless it is handed the events that happened during that history, and forced to read them first.
Closing the loop with a machine-readable change log
This is where the system stops being a one-way report and becomes a feedback loop. The Ads API exposes its own change history, so a setting Wayne flipped in the console shows up in recentChanges automatically. But not every intervention lives in the Ads API. When the operator runs a LINE broadcast for a summer promotion, or the analysis pipeline surfaces an empty shoulder-week and Wayne works the response into a sharper plan, none of that is visible to a model reading campaign metrics.
The business-context document the analysis AI reads is static. It only knows what it was told the last time someone updated it, which cannot keep pace with how often real operational decisions get made. That gap is structural — and it is exactly what operator-events.yaml exists to close. It is a continuously-appended, machine-readable event log of every off-platform intervention: a promotion broadcast, a pricing move, a manual outreach. The next analysis run reads it, so when inquiries climb in a given week, the model already knows a broadcast went out and won’t credit the lift entirely to the ad spend. The loop closes: the AI surfaces a problem, Wayne and a second AI deepen the response, the decision gets written into the log, and the next analysis run reasons with that context already in hand.
The alternative — letting the model infer interventions from noisy outcome data — is precisely the trap the whole exercise was built to avoid. You don’t fix a stale-input problem by asking the reasoner to guess what changed. You record what changed, in a form it can read, before it reasons.
Whether the loop holds depends on discipline: every off-platform move has to actually get logged. For now that’s a manual append, and it works because the person doing it understands why the next report depends on it. The honest answer is that the system’s accuracy is only as good as that habit — which is the same constraint that applies to any operations record that isn’t yet automated.