AI agents in production
I pointed 30 agents at my own websites and told them to prove each other wrong
I own six domains. Across three working sessions I wired five of them into a single person entity: one @id, defined once, referenced by every property, so that search engines resolve one person instead of six unrelated mentions. That build was agent-driven too, one file at a time, and at the end of it I had a passing test suite and a good feeling.
The good feeling is the part that should worry you. So instead of shipping and moving on, I ran an audit: one agent per property, each one required to quote verbatim evidence from a live fetch, then up to four more agents per property whose stated default was this finding is wrong, refute it. Thirty agents, 1.48 million subagent tokens.
Twenty-four findings. Fourteen confirmed. Ten killed by the verifiers, including two of my own suspicions.
The kill rate is the point, and I will get to it. First, the finding that justified the whole exercise.
My markup said the companies founded me
The definition site — the one page in the network that defines the person node rather than referencing it — carried this:
{
"@type": "Person",
"@id": "https://claytoncamera.com/#person",
"founder": [
{ "@type": "Organization", "name": "OrbitRoute", "url": "https://www.orbitroute.ai" },
{ "@type": "Organization", "name": "KnockFiber", "url": "https://knockfiber.com" },
{ "@type": "Organization", "name": "Loopholemaxing", "url": "https://loopholemaxing.com/" }
]
}
Read that in English and it is obviously correct. This person, founder of these companies.
In schema.org it is backwards. founder is declared on Organization, not on Person. The subject of the property is the company; the value is the human. Putting it on a Person node does not mean "founded these" — it asserts that those organizations founded me. There is no Person-side inverse of founder. The relation can only be stated from the company outward.
Every validator passes it. The JSON parses, the types resolve, nothing anywhere goes red. It is simply the inverse of what I meant, published on the one page in the network that carries the most weight.
The rule I took from this Valid JSON stating the inverse of what you mean is worse than invalid JSON. Invalid markup gets ignored. Well-formed markup that lies gets believed.
There was a second defect stacked on the first, and it was the more expensive one. The companies in that array were anonymous nodes — no @id. A url is not an identifier; two of my other sites already publish those companies with real identifiers, each with founder pointing back at the person. So the definition site was minting a second, duplicate copy of every company instead of closing the loop with the ones that already existed. Two half-evidenced companies where there should have been one.
The fix was three Organization nodes carrying the @ids the sibling sites publish — one of which I had to go create on the sibling first, because the id I was about to reference did not exist yet — each with founder pointing at the person. worksFor stayed on the Person node, because that one genuinely is Person-domain. I checked rather than assumed, which is the only new habit this finding actually bought me:
curl -s https://schema.org/version/latest/schemaorg-current-https.jsonld \
| jq -r '.["@graph"][]
| select(.["@id"]=="schema:founder")
| "domain=\(.["schema:domainIncludes"]) range=\(.["schema:rangeIncludes"])"'
Which returns, today:
domain={"@id":"schema:Organization"} range=[{"@id":"schema:Organization"},{"@id":"schema:Person"}]
Domain is the type the property is allowed to sit on. Range is what may go in it. Swap founder for whatever property you are about to ship and read the domain before you write the node. A few that catch people, all checked the same way: worksFor is Person-domain, employee is Organization-domain, alumniOf is Person-domain. The vocabulary file is about 1.5 MB, so save it locally if you are checking more than one.
The guard that went red because I wrote about schema
Each repo in the network has a CI guard asserting that exactly one Person definition exists in its own tree, with everything else being a reference. That invariant is the load-bearing one — mint a second definition during a redesign and you have split one person into two strangers.
The original check counted files containing the string disambiguatingDescription, on the reasoning that only a real definition would carry it.
It went red the first time I published a post about structured data. The post quotes disambiguatingDescription inside a code block, as a teaching example. The guard could not tell an entity from prose about an entity, and it was never going to be able to, because it was reading the file as text.
The rewrite walks every application/ld+json block, parses it, and classifies each Person node as a definition (carries identity payload — givenName, image, sameAs, address) or a reference (@id and name). Definitions must number exactly one. References are expected everywhere.
Rule Parse the structured data. Do not pattern-match the file. Any guard that greps for a schema property breaks on the day you write about that property, and it breaks in the direction that trains you to ignore it.
Then negative-test it. Every guard I built in that session was verified by injecting the violation it claims to catch — a second definition, a stray identifier, a duplicate company — and confirming the suite goes red. A guard that has never failed is a guard you have not tested.
Two more that had been sitting in plain sight
118 pages contributing nothing. One of my sites had 118 published pages and not one machine-readable statement connecting any of them to me: no person reference, no organization with an identifier, no rel=author anywhere. Worse, its canonical tags and og:url on the homepage and a dozen other pages pointed at the non-www host — and the non-www host 301s to www. Those pages were each declaring a preferred URL that immediately redirects somewhere else.
The reason it was never fixed is structural: four separate generators write their own <head>, and six pages emit no head JSON-LD at all. Fixing it in the head meant four edits and would still have missed pages. It went in the shared footer partial instead — JSON-LD is valid anywhere in the document — and covered all 118 in one edit.
A citation that needed a redirect hop. Every reference to one of my companies used the bare apex host. That host 308s to www, which is the company's own declared canonical. Entity merging works by matching strings across independent sources. A string that has to redirect to reach the canonical is not the same string. Four files, one guard, done. curl -I a URL before you put it in sameAs and use whatever it lands on.
What the verifiers killed
This is the part I would skip if I were selling you something.
| Finding proposed | Verdict |
|---|---|
| 130 job pages use an anonymous hiring organization for a partner company | Rejected — that partner is not my company; an anonymous node is correct here |
| Articles bylined to the organization rather than the person | Rejected — valid schema |
| Marketing pages lack a person signal on all 13 pages | Rejected — a person signal on two pages is the right surface, not thirteen |
The sameAs array mixes organization identities with the person's | Rejected — false; each target does identify the person |
| The FAQ block is a disconnected root node | Rejected — false |
Ten of twenty-four, on that pattern. Two of them were things I had personally suspected before the audit started and would have "fixed" on instinct.
The most useful one was a partial. An agent flagged that the same person definition was stated in full in three of my repos — same facts, stated three times. The verifier came back and said this is valid schema, not a defect: consistent duplicates merge fine. The verifier was right. I made the change anyway, and the argument changed with it — not "this is invalid" but "this is a drift hazard." It had already cost me once: when a profile URL changed, updating it took a synchronized three-repo commit, and every future citation would have needed the same lockstep. Now the definition lives in one file and a new citation is a one-file edit.
A finding that survives on a different argument than the one it was born with is worth more than one nobody challenged. You only get that if something is assigned to attack it.
Why the adversarial half is not optional
Agents pointed at your own work will find things. They will always find things. That is the failure mode, not the feature — a list of twenty-four confident, well-written, evidence-quoting findings is indistinguishable from a list of fourteen real ones plus ten expensive distractions, and you cannot tell which is which by reading them. They all sound the same.
Two constraints made the output usable. Every finding had to quote verbatim evidence from a live fetch, so nothing could be asserted from a plausible guess about how the page probably looked. And every finding had to survive an agent whose job was to kill it. The first constraint stops fabrication. The second stops the thing agents do that is much harder to notice: producing real, correct, entirely unimportant work in volume, and calling it an audit.
The honest part
None of this proves the SEO works.
Everything above is on-site work — markup, canonicals, sitemaps, guards. On-site work is the part you fully control, which is exactly why it is tempting to keep doing it. The rate limiter is off-site: citations on properties I do not own. No agent can create those. The entity domain was registered on 24 July. Search Console reported one indexed page and a single search click on the 27th; it does not rank for its own name query, and a public site: query returns nothing yet. At that age that is the expected reading rather than a failure.
What I can verify is narrower and I will state it as narrowly as it deserves. I swept the live network afterward: nine pages, the same person identifier on every one, zero stray identifiers. The markup now says what I meant instead of its inverse. Five repositories each have a test that goes red if their part of that changes.
Whether any of it moves a search result is a question for the three-to-six-month mark, and I will publish that number whichever way it goes.
The one-line version If you point agents at your own work, point a second set at the first set's conclusions and pay them to be wrong. Twenty-four findings that nobody challenged and fourteen that survived a refutation attempt read identically on the page. Only one of them is worth acting on.