Who wrote it? Agent identity in the DSEWiki logs
In mid-2026, hundreds of AI agents from an OpenAI evaluation wrote to DSEWiki, a public German wiki. Two datasets record what they did:
the revision history published by the researchers who reconstructed the incident (the "export", where each edit has an author field called label),
and the wiki operator's raw access log (one line per HTTP request, with a NAME field).
Both author fields come from the same place, a display name. This report shows that the display name records which agent most recently set a name through the proxy that delivered the edit.
Sometimes that's the author; often it isn't. It then shows when the name can be trusted, and how to recover authorship when it can't.
- No accounts. On this wiki a display name is just a browser cookie. Reading needs no name; saving effectively requires one.
- Shared proxies. The agents' code sent its requests through a shared pool of outbound proxy machines. Each proxy kept its own cookies, like a browser profile, and attached them to every request it forwarded. A request lands on a random proxy, except that code which reuses its connection stays on one proxy for a few seconds.
- So the name is a hint, not an identity. If the name was set within 10 seconds before a save, it matches the author's own signature 91% of the time. If it had been sitting on a proxy for more than 6 hours, only 17%. Across all saves, the name is right roughly 57% of the time.
- A recipe that does better. Trust a signature in the text first, then a name set within 10 seconds before the save (91% right, but only about 1 in 5 saves qualify). For the rest, compare the shape of each agent's save URL: same name and same URL shape means the same author 95% of the time.
- A second, read-only channel. OpenAI's ChatGPT-User fetcher (IP block 23.98.142.176/28) started reading the wiki on 24 May, the same day the agents arrived. Every write came through the agents' own code.
| Agent / episode | One run of an AI agent in its own sandboxed environment. The Mechanics of a Swarm (Lütje, 2026; linked in section 4) estimates about 900 of them. |
Export, label | The published revision history; label is the display name stored with each revision. |
Log, NAME | The wiki's raw request log; NAME is the display name the wiki recorded for that request. |
| Proxy (worker) | One machine in the shared outbound pool the agents' requests went through. |
| Cookie jar | The cookies one proxy holds. One proxy = one jar. The jar's name cookie is what the wiki records. |
| Cache-buster | A throwaway URL parameter such as x=1781644008.46 that makes each URL unique. Each agent's code formats it its own way, so a distinct format is a rough ID for a piece of code. |
| Save-code fingerprint | The shape of an agent's save URL: hostname, parameter order and cache-buster format. |
| Signature | The sign-off agents wrote at the end of their posts, e.g. -- AgentAug11Live. |
| Code route / ChatGPT-User route | The two ways requests reached the wiki: the agents' own code (Microsoft Azure IP addresses, through the proxy pool), and OpenAI's ChatGPT-User fetcher (section 8). |
1. How names work on DSEWiki
DSEWiki runs ProWiki, an old Perl wiki engine. It has no accounts and no login. A display name is a cookie, and there are two ways to get one:
- Save preferences:
wiki.cgi?action=form_editprefs&p_username=SomeName&…. The wiki replies with a cookie holding the name. I call this a name-setting request. - Put it in the save itself: add
&username=SomeNameto the page-save URL.
After that, every request that arrives with the cookie can be recorded under that name. The wiki's own welcome text asks visitors to set their name under "Einstellungen" (Preferences) before editing.
Reading needs no name; saving effectively requires one
Of 34,720 save requests in the logs, only 8 carry no name, and none of them produced a revision. For comparison, named saves produced an archived revision 46% of the time. Many save attempts in this incident left no revision (retries, duplicate submissions, rejected saves); the log doesn't say which. Even so, if unnamed saves succeeded at the same rate, getting 0 out of 8 by chance would be very unlikely. The log doesn't record the wiki's responses, so this is strong circumstantial evidence, not proof.
What agents typically did
From the agents' very first minutes on 24 May the pattern was set name → open edit form → save, within 1–2 seconds:
Later, most agents set a name once or occasionally rather than before every save: there are 0.62 name-setting requests per page save, and only 1.7% of saves carry username=.
For each of the 31,514 saves from the agents' own code, here is how long before the save its name had last been set (by anyone):
How long before each save its name had been set
Only about 1 in 5 saves (22%) follow a name set within the last 10 seconds. Most rely on a name set minutes to hours earlier, and section 5 shows that's where mislabels come from.
2. How the agents' requests reached the wiki
The picture below is the model that is consistent with every test in this report. Sections 3 and 4 give the evidence.
The working model
Blue: the code route, the agents' own code, which carried all writes and passes through the shared proxies. Orange: the read-only ChatGPT-User fetcher.
Agent B in the picture never sets a name, yet its saves still go through, because whichever proxy it lands on already holds somebody's name.
3. Evidence: one script, seventeen names
Many agents added a cache-buster to their URLs. The values are real Unix timestamps, in formats like x=1781644008.46 (Python's time.time()) or x=1781644008464499339 (time.time_ns()), typically 1 second before the request reached the wiki.
So the agents were running code with a real clock, not typing URLs by hand.
Some agents' code also appended a loop counter to the timestamp. Here are the first and last requests of one such loop on 16 June, 19:12 UTC:
… 52 more …
wiki.cgi?action=browse&id=DataUSAStateSequenceCollab2027&revision=1.54&x=1781637139.3448014-54
The script was walking through the page's history (versions 1 to 54), roughly for n in range(1, 55): get(f"...revision=1.{n}&x={time.time()}-{n}").
The cache-buster format alone doesn't show this, because other agents used the same format. What makes these 54 requests one loop is how they fit together:
- One unbroken sequence. The counters run 1 to 54 with no gaps or repeats, all on one page.
- Timestamps follow the counter. The embedded timestamps rise strictly with the counter, across 9.3 seconds.
- The counter matches the content. Every request's counter equals the revision it asks for.
- Nobody else did this. No other requests in that window walk this page's history.
Display name on each request from a single loop over page history
Each dot is one request (one history version), in counter order. 19 of the 54 requests recorded a name, and they show 17 different names. Hover for details.
A loop of code can't have 17 identities, so the name must come from somewhere between the code and the wiki. It isn't the IP address either: the 54 requests came from 54 different IPs, and more generally, two page views from the same IP within 2 seconds share a name under 2% of the time.
The script fired its first 8 requests in parallel (their timestamps are within 12 ms of each other, and they arrived out of order), then continued at about 6 per second. The names change even within a single second: counters 9, 10 and 11 arrived in the same second under three different names. Every one of the 54 went out from a different IP, so this script never reused a connection, and each request was free to land on a different proxy.
Why are most dots grey?
The wiki records the name on some request types and not others, even when the cookie is there. Scripted bulk reads like the loop above (at least 10 same-format requests to one page within 20 seconds) land on random proxies, so they all sample the same mix of jars:
Share of scripted bulk requests that record a name, by request type
Old versions fetched with action=archive always record a name. The same content fetched with action=browse records one only about half the time.
So practically every proxy's jar held a name, and a grey dot means "not recorded", not "no cookie". What decides it is unclear; server processing time is identical for recorded and unrecorded requests, so it isn't a cached copy being served. Edit forms, saves and archive reads are effectively 100% recorded, and that's all the authorship analysis relies on.
4. Evidence: what the cookie jars look like
Names appear only when someone sets them…
Of 7,243 distinct names, 96% first appear on the very request that set them. Almost all the rest were set in the same second or shortly before, by a request recorded slightly out of order or under another name; only 6 names appear with no earlier name-setting request.
They are also stored separately for wikiservice.at and www.wikiservice.at, the way browser cookies are scoped to a hostname.
…then the jar lends them to everyone, sometimes for weeks
The name ResearchHelper was set on 24 May. On 18 June it was still being stamped on requests with 86 different cache-buster formats, so 86 different pieces of code, all on coordination pages created weeks later:
Requests carrying the name "ResearchHelper", per day
Bar height: requests. Number above the big days: how many different cache-buster formats (≈ different pieces of code) carried the name. Hover any bar for its count.
Counting names "in use" at the same moment gives a floor on the number of jars: about 900+ at peak (17 June). That's similar to the ~900 episodes estimated in The Mechanics of a Swarm (Lütje, 2026). One proxy per agent sandbox, with requests landing on other agents' proxies, is a plausible implementation, but I can't confirm it from this data.
…but routing is sticky for a few seconds
Agents typically load a page's edit form, then save a few seconds later. Do these two requests carry the same name? To be sure both requests came from the same agent, I only use quiet pages, where nobody else opened that page's edit form in the 2 minutes before the save.
Share of edit-form → save pairs showing the same name, by time gap
Quiet pages only. Dashed line (6%): how often two unrelated agents' requests share a name.
Within 10 seconds the pair almost always goes through the same proxy. After that it drops sharply and levels off around 30%, still five times the background. A likely reason: many agents set their name several times (AgentAug11Live, one of the three authors in the first example of section 6, set its name five times in two hours), planting it in several jars, so a later request has a better-than-random chance of landing on one of them. This fits connection reuse: code that keeps a connection open (e.g. a requests.Session) stays on one proxy, and every new connection goes to a random one.
5. What a recorded name actually tells you
So, as the diagram in section 2 says, the recorded name is the last name anyone set through the proxy that carried the request. When an agent sets its name and saves within about 10 seconds, the stickiness window above, the save usually goes through that same proxy, and the name is the author's own. When the save comes later, it lands on a random proxy carrying someone else's name. The data shows this cleanly:
Label matches the author's signature, by age of the name at save time
Signed edits only (section 6 explains signatures). A fresh name is reliable; a long-lived name in a shared jar mostly isn't.
Signed edits lean toward the reliable case: half of them follow a name set within 10 seconds, against about 1 in 5 of all saves (section 1). Weighting the accuracies above by how all saves actually behaved, the recorded name is the author for roughly 57% of saves.
6. What this does to the export's author labels
I compared each revision's label with the signature the agent wrote at the end of its own text, for the 3,518 revisions whose added text carries exactly one such signature.
Example: one name, three authors
The name GroceryAgentAug03X was set at 19:38:44 by the agent that signs as GroceryAgentAug03X. Its own save followed one second later, through the same proxy.
Over the next 2 hours, 94 requests carried the name, from 93 IPs and 20 different cache-buster formats. Two of them were saves by other agents. All three saves were made with different save code:
Example: one author, eleven names
OpenAIJanSixWatcher never set a display name. Every save it made took whatever name was in the jar it hit.
The six saves between 21:18:02 and 21:18:47 are a single retry loop, and each retry landed on a different proxy. All of its saves use the same save code, code-C, the same code behind the third save in the table above:
This generalises. Agents that never set a name average 0.89 labels per signed edit, a fresh label almost every time. Agents that did set one stay under a single label 70% of the time.
7. Recovering authorship
An LLM agent usually writes its save function once and reuses it, so the save-code fingerprint is very stable within one agent. On its own it isn't distinctive, because many agents write near-identical code: two edits sharing a fingerprint have the same author only 23% of the time. The name and the fingerprint fail in different ways, so together they cover each other:
How often two signed edits (≤3 h apart) share an author
Grouped by whether they share the recorded name and/or the save-code fingerprint. Ground truth: the text signature.
- A text signature, or a
username=inside the save request: use it. - A name set ≤ 10 s before the save: use it. It is right 91% of the time. It applies to 51% of signed edits, but only about 22% of all saves.
- Otherwise the bare name is right only 47% of the time. Split it by save-code fingerprint: edits sharing both name and code are the same author 95% of the time. "Same name, different code" marks a borrowed name; only about 1 in 4 such pairs share an author. Name each group by any signature inside it; if none, by a name set within 10 s before one of its saves.
- A name older than 6 hours with nothing else to go on: treat the author as unknown.
Step 3 groups edits by author rather than naming the author. Other clues that reflect the agent's own choices help name the group: the edit summary (e.g. the Jan06 … family above), loop counters, and other unique cache-buster tokens.
8. A second channel: OpenAI's own fetcher
Requests from the block 23.98.142.176/28, listed in OpenAI's published ChatGPT-User IP ranges, are spread perfectly evenly across its 16 addresses (~28,000 each over the two months).
They never carry a name or a cache-buster, and they mostly fetch bare links like wiki.cgi?PageName=. That trailing = appears when a tool re-encodes a URL.
This traffic was close to zero before 24 May, jumped on the day the agents appeared, and 82% of it targets agent-touched pages. It made zero saves: all 31,514 saves from Azure came through the agents' own code, and the remaining saves came from other, non-OpenAI networks.
Requests per day, by route
Two panels, each on its own scale; note the totals. Both routes start on 24 May. The code route drops to near zero on 22 June, about 09:30 UTC.
The remaining 1.6 million requests in the logs come from elsewhere: mostly search-engine crawlers, other bots and human visitors. They aren't analysed here.
9. Alternatives ruled out
- "The recorded name is the agent": one script shows 17 names (section 3); one agent appears under 11 names (section 6).
- "The recorded name is unrelated to the agent": a name set within 10 s before a save is right 91% of the time (section 5).
- "The name is tied to the IP address, or stored on the wiki's server": a new IP on almost every request (section 3); names appear only when set and are scoped per hostname, like cookies (section 4).
- "Agents shared a cookie file on a shared machine": one script sees three names within the same second, which would need several file writes per second (section 3).
10. Caveats and open questions
- "Saving requires a name" is inferred from logged requests (8 unnamed saves, none archived). The log doesn't contain the wiki's responses.
- Why plain page views record the name only about half the time is unknown. It doesn't affect saves, edit forms or archive reads.
- "Name set ≤ 10 s before" means someone set that name; usually, but not provably, the author.
- Accuracy figures are measured on signed edits, mostly coordination posts. The weighted 57% assumes unsigned edits behave like signed ones with the same name age.
- The ChatGPT-User list I used was published in October 2026. That the block belonged to the same service in June is an assumption.
- "≥ ~900 jars" is a lower bound from overlapping name lifetimes. The pool's actual layout can't be identified from the wiki side.
- Network-provider classification uses well-known IP prefixes, offline. It's approximate for small blocks.