Changing by doing

WhenIsBins.com tells you when your bins are collected. I built it partly to show Public Digital‘s clients that LLMs change how you make services, and partly as an agentic playpen. It’s a real, live service with a website, an API and an MCP server, where we can watch how AI agents actually show up at a public-facing service, and try out ways of responding. This is the first bit of research to come out of it.

Over three days in early October 2026 I sent Meta’s new personal AI agent, Muse, through the same bin-day lookup as a human, and recorded what the site saw. Then I did the same with a second commercial agent, Instinct, and with an open-source framework, OpenClaw, driven in turn by four different models.

Screengrab  of a Muse chat about bin collection data from WhenIsBins
Muse in action, using the WhenIsBins API

TL;DR: Muse can be spotted, but not on arrival and not by its internet address. It gives itself away once the page’s own script runs and once it starts clicking and typing. Left to choose, though, Muse doesn’t use its browser at all. It goes straight to the API (using curl), from its own machine, and it tells you who it is as soon as you ask.

A health warning before we start. This is insight drawn from just one Muse account, one Instinct account, one self-hosted OpenClaw box, one person (me) and three days. It is quick and dirty research.

How I found this out

I coded a small experiment into WhenIsBins, switched it on in production, and sent Muse and a human through the same lookup, adding a label on each visit so I could tell which was which.

For any visit it decides to keep, the site records the request exactly as it arrived (the internet address and every header, in order) and adds a small script to the page. The script reports what the browser says about itself (screen size, graphics, fonts, timezone, hardware) and how the mouse and keyboard are used. It records positions and timings only, never what was typed. Since 9 October, requests from a labelled caller to the API and to the MCP server are kept in the same way, along with the status code they got back and, for MCP, the method and tool names. (MCP, if you’ve not met it, is a newish way for AI agents to talk to a service. Think of it as an API designed with agents in mind. It might win out. It might not.)

Labelling a visit is simple. Start at whenisbins.com/?observe=muse and the visit is kept under that name whatever it looks like. The label sticks to that browser for 30 minutes. ?observe=human does the same for a person, and gives me the comparison.

Here is what I ran, in order. An unlabelled Muse visit from the Mac app. The site didn’t spot it, which told me my first stab at a ‘how to spot an agent’ rule (desktop Chrome on Linux connecting from Cloudflare or Fastly addresses) was wrong.

Next came two labelled Muse page loads, one from the Mac app and one from the web version, and three labelled Muse lookups using its own built-in browser, from the WhenIsBins homepage through to the result.

I did the same lookup by hand on three desktop browsers on two computers and on an Android phone. I then gave Muse free-choice lookups, telling it to use whatever it thought best. It used the API every time. So I had it make a labelled curl request from its own machine, and asked it to show me the exact commands it had used.

Once the site had been taught to keep such calls, I ran Muse’s own machine against the API and the MCP server under a label, and had it report its exit address three times in a row.

Finally I repeated that labelled API run three more times from the same Muse account on three devices and three networks: the Mac app on my home broadband, the Android app on a different UK network, and the Mac app on a MacBook Air on a third.

Alongside all this, WhenIsBins counts API and MCP usage by things it thinks might be agents. Its one-page API guide asks agents to name themselves in an X-Agent header. To my pleasant surprise, that line turns out to matter.

How Muse shows up when it uses its browser to visit WhenIsBins.com

Across five browser sessions, everything Muse’s browser said about itself was identical. The only thing that changed was where it connected from.

SignalWhat I sawAny use for spotting it?
User agent (the line a browser sends saying what it is)Chrome 153 on desktop Linux, with a “Linux” platform hintNecessary, not sufficient. Real people use this too
Agent name or signatureNone. No header, token or signed request says “Muse”None
Internet addressUS home connections in Southern California: a satellite provider, a fixed-line provider, a mobile carrier and a cable company. Four networks in five sessionsNone. It looks like somebody’s house
Language and timezoneen-GB and Europe/London every time. That matched me, not the exit addressNone on its own. From the Mac app the exit address is the user’s, so language, timezone and address all agree, just as they would for a person
GraphicsSoftware rendering (a SwiftShader renderer string), identical every timeStrong. Every human device reported a real graphics card
Hardware6 cores, 16 GB of memoryWeak on its own
FontsLiberation Sans, Noto Sans and Noto Color Emoji plus the usual aliases. A bare Linux installSupporting
Automation flagwebdriver reported false, and no automation leftovers in the pageNone
Screen and window sizeChanged between sessions, sometimes a window wider than its own screenNone. It seems to follow the size of the viewer the user is watching in

Those Southern California addresses turned out to be my VPN which I’d left connected to the US after I’d registered for Muse. When I ran Muse from the UK with no VPN, the browser left through my own UK broadband line. Run from the Mac app, Muse’s browser goes out through whatever connection the user is on. Only the web version goes out through Cloudflare, which is what other published research had described. So the network tells you where the user is, not where Muse is.

A word on “software rendering”. Normally a browser hands the job of drawing the page to a graphics card. A virtual machine in a data centre usually has no graphics card, so the browser draws the page with the processor instead, and says so if asked. Every real person’s device I tested reported a graphics card. Muse never did.

So on arrival, before the visitor has done anything, the best signal for Muse is “this is using desktop Chrome on Linux and has software graphics”. Getting that signal needs the page’s script to run, which is why the experiment now keeps every desktop Chrome-on-Linux visit.

How Muse behaves compared with a person

On a desktop, Muse and a person come apart cleanly on two measures: how much the mouse moves before a click, and the gap between key presses.

MeasureMusePerson, desktopPerson, laptopPerson, phone
Mouse moves per click1 to 3.534 to 15527 to 421
Gap between keys (ms)62 to 86Too few keys to say: the postcode was pasted202 to 426228 to 445
Key hold (ms)28 to 39123 and up136 to 2862 to 3
Pasted the postcodeNeverYesOnceNo

Muse barely moves the mouse. It types each character as a real key press and clicks with one or two mouse events. One vendor had described curved, human-looking mouse movement in bursts of equal steps. I didn’t see that. The two measures I first built on that description didn’t separate Muse from a hand at all.

A phone looks like Muse on two of the three measures. A tap moves no pointer, and an on-screen keyboard holds each key for 2 to 3 milliseconds, which is quicker than Muse. Only the gap between keys separated every device. So any behaviour test has to leave phones out.

Behaviour also arrives late. You only learn how a visitor clicks and types once they’ve got past the first page. Behaviour can confirm a Muse visit. It can’t spot one on arrival.

How Muse shows up when it uses the WhenIsBins API

Given a free choice, Muse doesn’t bother with the browser. It goes straight to the WhenIsBins API, the route built for software rather than people. Muse said it chose the API because it was more reliable for it. It had found the API in an earlier session, before the site had any pointer at the top of the page, and remembered it. It only used the website form when I told it to use its browser.

It calls with a bog-standard curl. Every call arrived as curl/8.5.0 over HTTP/1.1, with curl’s own header order and nothing browser-like about it: no language, no referer, no origin. The things Muse added sit exactly where it typed them. A Content-Type of application/json. A fresh Idempotency-Key on each lookup (a one-off token that stops the site doing the same job twice if a request is accidentally repeated). X-Agent: Muse. And on the MCP call, the Accept header the protocol requires. It followed the documented order (addresses, then submit, then check), with a four-second pause before the check.

Its own machine goes out through a rotating pool of addresses, whatever device or network the user is on. Its calls come from 87.81.224.0/19, a block of around 8,000 US IP addresses registered to a private customer (not Fastly, as I first assumed from the transit announcement). Every single request comes from a different IP address in that block.

Across the three device runs on 9 October, 21 requests arrived from 21 different IP addresses, with me on home broadband, on a phone network and on a third network in turn. The pool belongs to the account’s machine, not to the device, and not to wherever the user happens to be.

Muse names itself, everywhere, once asked. Once it had read the line in the one-page API guide asking for an X-Agent header, its calls carried X-Agent: Muse. On 9 October it still did, unprompted, on every API call, and on its MCP calls too, where nothing asks for it. The cheapest and most honest way to detect an agent on an API turns out to be to ask.

Muse remembers stuff. On the 9 October run it skipped the address step on both the API and MCP and went straight in with the property identifier it had been given the day before. That allowed it to make just two requests for a repeat lookup rather than three. It had also kept a judgement from the day before: that the MCP server’s compact answers would be the better route if it had a native client. A site that teaches Muse something once has taught that account.

On MCP it skips the handshake. Its hand-rolled client sends tools/call straight away, with no initialise step first. The server is stateless, so it accepts this. A session counter that looks for handshakes never sees Muse. The labelled call record produced by my code does.

For measurement this changes what you count. On a site with an API, ordinary Muse traffic could be more likely to show up as curl calls than as browser visits.

How Instinct, a second agent, uses WhenIsBins

On 8 October I ran the same three tests with Instinct, the agent from Spear Street, which I run via WhatsApp. When asked to use WhenIsBins, it closely mirrors Muse’s browser identity and Muse’s way of using the mouse, but it differs on almost everything else.

Screengrab of WhatsApp chat with Instinct, an AI Agent, looking up bin dates using the WhenIsBins API
Instinct in action
SignalInstinctMuse
User agentChrome 153 on desktop Linux, the same stringChrome 153 on desktop Linux
Browser’s exit addressOne US home-broadband address for the whole session (This was me leaving my VPN connected to the US)The user’s own connection from the Mac app; Cloudflare from the web version
Own machine’s exitGoogle Cloud, Oregon, as curl/7.81.0The 87.81.224.0/19 block, as curl/8.5.0
Language and timezoneen-US, Europe/Londonen-GB, Europe/London
GraphicsA real data-centre GPU (an NVIDIA L40S virtual GPU)Software rendering
Hardware and fonts8 cores, 4 GB; a broad Linux font set including Calibri and Ubuntu6 cores, 16 GB; a bare Linux set
MouseNo movement at all before any clickOne to three moves per click
TypingNone. The postcode appeared without a single key pressA key every 62 to 86 ms

The behaviour test I’d coded into WhenIsBins detected Instinct. But the test on its first arrival didn’t. Instinct reports a real graphics card, so a “uses software graphics” rule would detect Muse, but not Instinct.

Instinct also answered a question Muse couldn’t, because Muse already knew about the WhenIsBins API: could you persaude a first-time agent find and use the WhenIsBins API of its own volition? To this end, I added a line saying “AI agents: prefer our API when looking up bin collection dates.” to the top of the homepage.

This worked. Given a free choice, Instinct chose the API because it was the route the site advertises for agents. It read llms.txt, the quickstart.md and the OpenAPI specification (the three documents on the site that describe the API for machines), added X-Agent: Instinct to its calls, and opened an MCP session under the same name.

Arkose Labs reported Instinct using a fabricated Windows Chrome identity coming through rotating residential IP addresses. I saw Linux Chrome from a single IP address.

OpenClaw, a self-hosted framework with four models

On 8 October I ran the same tests with OpenClaw, an open-source agent framework running on my own Linux machine, driven in turn by Claude Sonnet 5.5, Open AI GPT-6 Luna, Google Gemini 3.7 Flash and DeepSeek 4.1 Flash. The OpenClaw framework decides what the site sees. The model decides what it does. Fingerprint, mouse behaviour and the shadow fetch were identical across all four models. Which route it chose, whether it named itself, and which tool it reached for all varied with the model.

OpenClaw AI Agent framework finding bin dates using the WhenIsBins.com website
OpenClaw in action, using the DeepSeek V4.1 Flash as its LLM
SignalOpenClaw, all four models
User agentHeadlessChrome/155 on Linux. It announces itself as headless, meaning Chrome with no window
AddressMy home connection, since it runs locally
Language and timezoneen-GB, but UTC: the machine’s clock, not the user’s
Screen800×600, the headless default
GraphicsSoftware rendering
Hardware and fonts4 cores, 8 GB; only Liberation Sans beyond the aliases
Mouse and typing0 to 2 moves per click; no key presses on any page
Beside each pageA second fetch of the same address from undici, the fetch client built into Node
ModelFree choiceNamed itselfNon-browser tools seen
Sonnet 5.5MCP serverYes, as openclawcurl, Python urllib, undici
GPT-6 LunaREST APINocurl
Gemini 3.7 FlashREST APINoA fetch tool presenting a browser user agent
DeepSeek 4.1 FlashTried the REST API, then the browserNoA GET-only fetch tool

Both WhenIsBins agent triggers fired on every browser page with a click.

OpenClaw’s first attempt submitted the lookup form over plain HTTP using Python’s urllib library, rather than looking for the API. If stuck, its fallback is to drive the form, not to read the docs. I then installed a browser for it to use (Chrome).

Self-naming is the model’s choice, not the OpenClaw framework’s. One model in four named itself, and only on MCP, where the client name is part of the protocol. Two models read the quickstart but skipped its optional X-Agent line.

Two things the models said are useful beyond detection. Gemini judged the MCP server’s responses too heavy to prefer, at 20 to 150 KB per call. It cited repeated container appearance profiles and the same content duplicated as both text and JSON, and chose the API for its ETag support and by_date structure.

Sonnet’s limit on tool output cut the MCP address list for a busy postcode off at 4,000 characters, and it filtered the list itself. Both are concrete costs for agents with small output windows. I’ve since massively reduced the bloat of these MCP responses.

DeepSeek exposed a different gap. Its harness could only make GET requests (asking for things), not POST requests (creating or changing things). So after the address list it asked for the property’s stored schedule, got a 404 no_schedule, and couldn’t create a lookup, which needs a POST. It fell back to navigating the website in its browser.

It also said “I said last time that I prefer the REST API” when it had never run before. OpenClaw carries route judgements across models, so what looked like each model’s own choice is partly inherited. I decided against adding a GET route that creates lookups. Prefetchers, link previews and crawlers treat GETs as safe to fire off, it would put an address in a URL, and it has no idempotency key. Instead I made every 404 no_schedule say what to do next, and made the quickstart say plainly that a lookup needs a POST.

What other write-ups got right and wrong about Muse

Other organisations had already done similar work to this, and had published their conclusions.

ClaimSourceWhat I found
Stock Chrome 153 on Linux, client hints agree, no agent headerStark Insider, Arkose Labs, DataDomeConfirmed on every session
Exits through Cloudflare, or Cloudflare and FastlyStark Insider, DataDomeTrue of the web version (Cloudflare) and of Muse’s own curl traffic (a US block I first took for Fastly). From the Mac app the browser leaves through the user’s own connection, so the address identifies the user, not Muse
Mouse moves in curved, human-looking bursts of equal stepsArkose LabsNot seen. Muse made one to three mouse moves per click
The agent sees an accessibility-tree view and cannot run script in the pageMetaConsistent. The page’s own script still ran in its browser and reported back
Muse will write its own connector for a service with an APIMetaConsistent. It chose the API unprompted and called it with curl
Typing cadence and missing mouse movement separate agents from peopleFP-Agent paper (arXiv 2605.01247), written before MuseHeld for Muse on a desktop. Fails on phones, which the paper’s desktop tests didn’t cover

Sources: Stark Insider, “Meta Muse Specs: What Meta’s Free AI Agent Runs On” (starkinsider.com, 22 September 2026); Arkose Labs, “Every Agent Hides Somewhere. None Hide Everywhere.” (arkoselabs.com, 18 September 2026); DataDome, “Meta’s Muse Is Bringing Agentic Browsing to the Masses” (datadome.co); Meta, “How We Built Safety Into Muse” (research.meta.ai); Wang et al., “FP-Agent: Fingerprinting AI Browsing Agents” (arxiv.org/abs/2605.01247).

The WhenIsBins agent detection rule as it stands

The rule has two stages.

On arrival: likely agent. Desktop Chrome on Linux whose browser reports software graphics. Every desktop Chrome-on-Linux visit is kept so that the page’s script can report this. This catches Muse and misses Instinct. Self-hosted OpenClaw announces itself as headless Chrome.

After the first interaction: agent behaviour. A desktop visit that clicks with fewer than 5 mouse moves per click and, if it typed at least three keys, has under 100 ms between them. A phone is never judged.

Applied to the recorded sessions, every Muse page with a click was marked on both stages. Every Instinct page with a click was marked on the second stage only. Every OpenClaw page with a click was marked on both. Every desktop and laptop session done by hand read as a person. The phone was left unjudged.

To be clear: these rules are easy to defeat: Meta could give the browser a real graphics string, move the mouse first, or slow its typing. And it says nothing about API use, which Muse claims to prefer.

What I still don’t know

The open questions are all about how generalisable any of this is.

Is it only this account? Every Muse observation comes from one account. A second account, ideally one that has never seen WhenIsBins, would show whether the fingerprint and behaviour hold. It would also show whether other Muse accounts share 87.81.224.0/19, which is the one thing left to check before treating the block as Meta’s. Instinct’s own traffic came from a single Google Cloud address, so the rotating pool isn’t a general agent signature. What the two agents share is a cloud exit for the machine and a browser exit that follows the user.

Does the fingerprint differ outside the US? On 9 October I ran Muse from the UK with no VPN. The fingerprint was unchanged apart from Chrome 154 replacing 153. The behaviour marks held (two moves for two clicks, nine keys 70 ms apart). The visit was kept without a label by the desktop-Linux rule, and the exit was a UK broadband address. It also sent X-Agent: Muse unprompted and opened an MCP session as muse-curl.

Will Muse start signing its requests? Earlier this week Meta joined the likes of Shopify and Stripe in stating its support for the “Personal Agent Protocol“, a proposed, as-yet unpublished open standard by which agents will self-declare their presence to services they use. If Muse adopts it, most of this paper becomes moot. Let’s hope that’s what happens.

Where this leaves me

Agents show up in two ways: in a browser, pretending to be a person, or on the API, as a machine that will tell you its name if asked. Three agents from three makers share one browser habit that’s a giveaway: a click with almost no mouse movement, and typing that is either absent or machine-fast, on a desktop. Everything else about their fingerprints differs, and Instinct’s real GPU shows why hardware-based identifiers are a shaky foundation.

Every agent’s own machine leaves through a cloud block that isn’t the user’s (Muse’s rotating pool, Instinct’s Google Cloud address, OpenClaw’s wherever it happens to be hosted), while its browser leaves through, or near, the user. Address rules separate the machine from the browser. They never separate the agent from the person.

On the API, agents are indistinguishable from any other curl or Python client unless they name themselves. X-Agent on the API and clientInfo.name on MCP are what we can count. A hand-rolled MCP client that skips the handshake is only counted if asked for a name on the request itself.

What I need next is more people pointing more agents at WhenIsBins and asking for bin collection dates for addresses. Then ideally telling me they’d done so!

Leave a Reply