I built Pixel Patrol for the All Things Agentic hackathon, and this post is part of that submission. Code is at https://github.com/thatmike1/pixel-patrol.
The problem
Cookie policies are documents about software, and they go stale the moment the software changes. Someone in marketing adds a Meta Pixel through the tag manager on a Tuesday afternoon. Nobody tells the person whose name is on the privacy policy. From that afternoon on the site processes personal data it does not declare, and under the GDPR that is the owner's problem, not the marketer's.
Everyone knows the fix. Check the site now and then, update the policy when something changed. Nobody does it, because the checking is boring and the updating is worse. That is not a chatbot-shaped problem. The work has to happen with nobody watching and land as a finished document where work already gets tracked.
What it does
Once an hour, Pixel Patrol crawls every site it is responsible for with a real browser (Playwright, in a Cloud Run Job). It records every third-party host and every cookie, both before and after the consent banner is dismissed, and compares the result with a baseline someone approved.
When a tracker appears that nobody decided to add, it identifies the vendor, writes the cookie policy change in Czech as a redline, writes the Records of Processing Activities row the owner is now obliged to file, opens a GitHub issue with both, and emails the owner. No dashboard. The first you hear of it is a ticket in the tracker you already use, with text you can paste.
The part that took the time
A raw diff of two host lists is not an alert. On a commercial site the third-party set moves on its own: programmatic ad slots fill with a different vendor on every pageview, and CDN hosts rotate shard names between crawls. An agent that fires on that gets muted inside a week, and a muted watchdog is worse than none.
So hosts are compared by registrable domain, not hostname, and every new domain and cookie is classified against the site's own recent history before anything is reported. On a Czech news site, across 69 real decisions, it saw 643 differences and reported 12. No domain was reported twice.
The split between "this is noise" and "this is drift" is deterministic code. The model never gets to redraw it. Gemini 3.5 Flash, running as two Google ADK agents on Vertex AI, does the judgement on top: one agent decides whether a difference is a first sweep, harmless rotation, or a real tracker and explains why in language the site owner can act on; the second writes the redline and the RoPA row, and it only runs when the first one recorded drift. Everything else, from the diffing to the deduplication to filing the ticket, is plain TypeScript the model cannot reach around. An LLM asked to eyeball two host lists will occasionally miss one, and a missed marketing pixel is the one failure this thing exists to prevent.
The bug I am glad I caught
One of the demo pages loads toplist.cz, a real and fairly obscure Czech hit counter. It is there to prove the system will say "I do not know who this is" instead of guessing.
The first run named Mailchimp as the operator, medium confidence, with a written basis pointing at the table entry list-manage.com sharing a brand token. The model had followed its instructions exactly. The evidence I handed it was wrong: the near-match lookup accepted any four-character substring, so "toplist" contained "list" and Mailchimp became a neighbour. One loose includes, and the output was an invented vendor in a document meant for a regulator.
The fix matches whole words, the exact case is pinned by a test, and the answer is now vendor: null, unclassified, low confidence, with a redline telling the owner to find out who runs the domain before publishing anything.
One more, because it was invisible
The dead-letter policy was configured, the IAM was right, and a poison message really did get published to the dead-letter topic. Then it vanished. Pub/Sub delivers to every subscription on a topic and drops the message, so a dead-letter topic with no subscription retains nothing, and everything reports success while the evidence is gone. Both dead-letter topics now get a pull subscription with seven-day retention before the first bad message can arrive.
Stack
Gemini 3.5 Flash on Vertex AI through the Google ADK. Cloud Scheduler, Pub/Sub with push subscriptions and OIDC tokens, Cloud Run for the agent service, Cloud Run Jobs for the crawler, Firestore, Secret Manager, Cloud Build and Artifact Registry. TypeScript on Node 22, 135 tests under node --test.
It has been running unattended on five demo sites and two real ones, filing tickets, while I wrote this.
This article was originally published by DEV Community and written by Michal Pšenčík.
Read original article on DEV Community