WordPress 7.0 quietly became agent-ready. The Abilities API is in core, the MCP Adapter exposes those abilities to any AI client, and Automattic’s new agents-api plugin defines what an agent, a session, and a scheduled “routine” are. But read the fine print and you’ll find a deliberate hole: it ships no runtime. Their own code comments say it — agents/chat is “a dispatcher… agents-api itself ships no chat runtime.” No scheduler beyond a suggestion to install Action Scheduler. No model access. No durable storage.
That hole exists because PHP can’t fill it. A WordPress server only thinks when someone visits it. WP-Cron fires on page loads; there is no persistent process; an “agent” that’s supposed to act on its own schedule has nowhere to live.
So I spent a few days proving it could live somewhere else: a Cloudflare Durable Object — a tiny stateful compute instance that hibernates for free and can wake itself with an alarm. Here’s the journey, spike by spike.
Spike 1: does the core loop even work?
The riskiest claim first: an off-host Durable Object wakes on its own alarm (WP-Cron disabled, zero traffic to the site), authenticates with a deliberately weak credential — a Contributor user that can create drafts but never publish — and mutates a real WordPress site through the Abilities API.
It worked. Alarm drift was within ~1% across every run, and the draft appeared with the alarm’s exact timestamp. It also surfaced nine real integration gotchas (SDK churn, OAuth probes, proxy SSL quirks) that shaped everything after.
Spike 2: who hosts this thing?
Studying existing WP×Cloudflare plugins (the WAF-manager genre) revealed that users will happily paste a scoped Cloudflare API token into wp-admin. That inverted the whole business model: what if the plugin deploys the runtime into the user’s own Cloudflare account?
Proven with raw REST calls only — the kind PHP can make: one multipart upload deployed a Worker with a Durable Object binding and SQLite migration; the workers.dev route went live in seconds. No wrangler, no Node, no CLI. The runtime costs the vendor nothing; compute and inference bill to the site owner’s account (mostly within free tiers).
Spike 3: the daemonless broker
The plan had assumed a tunnel daemon on the WP host — impossible on most hosting. The inversion: a WordPress site is already a public web server. So the Durable Object pushes an HMAC-signed job to a REST endpoint the plugin registers. The plugin verifies the signature (timestamp-bound, replay-protected, idempotent by job ID) and executes the job locally under the scoped user — which means a compromised Worker holds no WordPress credential at all; the site enforces its own capability checks on every job.
This killed four problems at once: no Application Passwords, no MCP OAuth fight, no daemon, no tunnel. The test run: alarm fired 1 ms off schedule, the agent requested publish, and the site downgraded it to draft because the scoped user lacks the capability. Security enforcement you can demo.
Phases 4–7: from plumbing to product
- Phase 4 turned the spikes into a real plugin: paste a token (used once, never stored), PHP deploys the Worker, done.
- Phase 0.5 — re-reading
agents-apirevealed the right architecture: don’t invent agent concepts; be the runtime theirs is missing. Our plugin registers the chat handleragents/chatis waiting for, and replaces Action Scheduler as the routine backend through their own lifecycle hooks. - Phase 5 shipped the first real agent: a “site columnist” routine whose Durable Object wakes, runs Workers AI (llama-3.3-70b — on the site owner’s account, no API keys anywhere), and files a complete draft post. It ran every 2 minutes for 3 hours unattended: ~87 fires, 100% delivery.
- Phase 6 fixed what that run exposed. The agent kept writing the same column — agents-api reuses a session across wakes by contract, but nothing remembered. Now the DO keeps a SQLite transcript per session, and four consecutive runs produced four distinct topics. And because 87 unattended model calls is a bill waiting to happen, routines now self-terminate at
max_runs, pause after repeated failures, and a hard daily inference budget returns 429 when spent. - Phase 7 added the wp-admin dashboard: live schedule state read from the Durable Object, pause/resume/run-now buttons that drive agents-api’s own registry (so any plugin using the standard hooks drives the off-host scheduler without knowing it exists), transcript viewer, budget meter.
The field test
Today: two zips uploaded to an ordinary live VPS WordPress site. Activate, paste token, click Provision — the plugin deployed a site-specific Worker and wired it through an AI Gateway (caching, logs, kill switch) it created itself. Click Run now.
Sixty seconds later there was a model-written draft on a production site. The push crossed real DNS and a real WAF with no tunnel and no special setup; alarm drift was 0 ms; the schedule self-terminated after its four allowed runs.
The shape of the thing
CLOUDFLARE (persistent, ~cents/month, owner's account) WORDPRESS (alive only per-request)
───────────────────────────────────────────── ──────────────────────────────────
Durable Object: alarm, transcript, budget ──push──► verify HMAC → agents-api dispatch
Workers AI via AI Gateway ◄──signed call── → chat handler → capability-checked
draft, written as a no-publish user
WordPress stays the system of record and the permission boundary. Cloudflare is the heartbeat, the memory, and the brain. The credential story is the part I’m proudest of: the CF token is used at provision time and discarded; the Worker holds no WP credential; the site executes every job under a user that structurally cannot publish or delete anything.
WordPress finally has hands and a brain interface. This gives it a pulse.
Built on WordPress 7.0, Automattic’s agents-api (v0.2.1), Cloudflare Workers, Durable Objects, Workers AI, and AI Gateway.





