Site icon Artur Piszek

PHP can AI: WordPress.com Agentic Infrastructure

This post describes how we designed the Automattic / WordPress.com AI Agent “Framework”, how it’s running (in PHP!), and what trade‑offs we made along the way. If there is one point I want to get across, it is that agentic frameworks are dime a dozen, agents are LLMs calling tools in a loop, and absolutely the hardest part about working on agentic frameworks is resisting the urge to overcomplicate things (per the AIKEA effect).

Automattic, like everybody has introduced a suite of AI features to our products. Prepare for a lot of sparkle emoji.

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

We got a lot more cooking, but you have to wait for the announcements.

Diverse architectures

The architectures of these features follow the gold rush of the AI era: We shipped what was fast, mostly directly on top of the OpenAI API, and that simplicity proved to be an excellent choice. It allowed us to iterate quickly, find what resonated with users, and integrate new APIs.

What proved to be challenging is not these ad hoc implementations, but when we tried to optimize prematurely. Either using frameworks that we didn’t really need or trying to predict the future by laying foundations for the APIs that never materialized. Future-proofing is a fool’s errand.

Regardless of reasons, we ended up with a few different implementations:

All these choices were locally optimal, but they didn’t provide a cohesive experience and they didn’t reinforce each other.

Why? The AWS Manifesto

Inside Amazon there is this famous internal “API mandate” memo (often called the AWS manifesto): every team must expose its data and capabilities only through well‑defined service APIs, no direct database calls, no backdoors, and the same interfaces must work both internally and externally.

That painful mandate forced Amazon to transition from ad-hoc locally optimal implementation to composable generic services: and once those services existed, it became “obvious” that you could rent them out to the rest of the world as primitives, which is how AWS appeared as a product.

This is the mental model we’re chasing at Automattic: invest in reusable building blocks (sometimes quite literally) so that improving one piece (payments, search, agents, etc.) automatically makes every product better.

I wrote more about this lens in Composability is the only game in town, and we used the same trick when running Tumblr on WooCommerce instead of inventing a one‑off billing stack.

One architecture to rule them all

Yours truly presenting this simple and elegant architecture to the Automattic AI division.
My colleagues appreciated the clarity of my explanation with a flattering meme.

PHP can AI

The most controversial decision we made was to lean into our main stack: run the agentic framework in PHP, next to the rest of WordPress.com complex infrastructure which is WordPress on bare metal, spread around 28 data centers.

Before this, our AI landscape looked more or less like this:

This microservices approach “worked”, but it didn’t generate the flywheel you want in a big product:

By bringing the agentic layer into PHP, we got:

Is PHP the “coolest” language for AI? No.

Does it matter when most of the “AI” is an HTTP call to a model and the hard part is orchestration, safety, and product integration? Also no.

What we’re actually building: agents, abilities, and context

Working with AI is really like delegating:

To help People
succeed
To help your AIHow Automattic
does it
You need to empower your teamYou provide toolsWP Abilities
You need to give them space to actYou put it in agentic loopclass.agent.php
You need to give them enough informationYou provide prompt + ContextContext hydration
You listen to their complaintsTracesLangfuse

Or if you really like charts:

graph TB
    subgraph "Frontend"
        CLIENT[Agenttic Client]
        USER[User Input]
        FE_TOOLS[WP_Ability<br/>JS]
        CONTENT[Post Content]
        WPDATA[wp.data]
        CLIENT <--> USER
        CLIENT <--> FE_TOOLS
        CLIENT <--> CONTENT
        CLIENT <--> WPDATA
    end

    subgraph "Backend"
        subgraph "Agent"
            PROMPT[Prompt]
            LOOP[Agentic Loop]
        end
        CTX[Context]
        BE[WP_Ability<br/>PHP]
    end

    LLM[LLM]

    CLIENT <-->|A2A protocol| LOOP
    CTX --> PROMPT
    PROMPT --> LOOP
    LOOP <--> LLM
    LOOP <--> BE

Abilities

You can think of Abilities as something like Agent Skills for WordPress, with bundled tools.

We introduced it into WordPress Core in 6.9 release and this API design was guided by the needs of the infrastructure you are reading about right now.

Abilities are self-contained, ideally stateless workers that perform a task:

Abilities API will be WordPress’ moat in the AI world. Products are easy to vibe code nowadays but the ecosystem of connected, tested and safe tooling for AI agents is not.

Frontend Abilities

Regular abilities run in the PHP environment on top of WordPress, with access to database and all WP API.

That is not enough. After rewriting the editor into WordPress blocks, our users expect interactive and snappy experiences in the editor, and that also includes AI interaction.

Abilities API introduces Generative UI via frontend (JS) tools to interact with current WordPress editor canvas.

In our Agentic Framework:

Some examples of Frontend abilities we use for our AI Website builder include:

Agents

Under the hood, LLM agents are just LLMs in a loop calling tools. Yes, I am tired of the hype too.

  1. In marketing speak, agent implies that it’s gonna do everything for you, work independently and require no supervision.
  2. In practice, we call everything that has a loop an agent.

And you know what? As models are getting better, both of these are true.

Source via Simon Willison

As I mentioned – we started by implementing Agents on top of Claude Code SDK, but that proved challenging to orchestrate at our scale. We replaced it with a 3-component class.agent.php

Are you ready for a secret Automattic Agentic Loop™️ ?

/**
 * Run the agent on the specified backscroll. This will call tools if needed, and loop until the agent is done.
 *
 * @param Message[] $user_backscroll List of messages representing the conversation history.
 * @return Message[]|WP_Error The conversation history with the agent's responses appended, or an error on failure.
 */
public function run( array $user_backscroll ): array|WP_Error {
	$max_loops = $this->max_loops;
	$this->log_pre_run( $user_backscroll, $max_loops );
	$tool_results = array();
	do {
		--$max_loops;
		$response = $this->complete_backscroll( $user_backscroll );
		$tool_results = array();
		$user_backscroll = array_merge( $user_backscroll, $response );
		foreach ( $response as $message ) {
			if ( $message instanceof Tool_Call ) {
				$this->log_tool_call( $message );
				$tool_result = $this->call_tool( $message );
				$this->log_tool_call_result( $message, $tool_result );
				$user_backscroll[] = $tool_result;
				$tool_results[]	    = $tool_result;
			} else {
				$this->log_updates_callback( $message );
			}
		}
	} while (
		$max_loops > 0
	&&
		! empty( $user_backscroll )
	&&
		self::get_last_message( $user_backscroll )->should_resubmit()
	);
	$this->log_after_run( $user_backscroll );
	return $user_backscroll;
}

Where is the reasoning and intelligence?

Ah! Surely you are thinking of our secret thinking ability!

'execute_callback'    => function ( array $input ): string {
	// The incredible thinking tool works like a rubber duck.
	// It forces the LLM to verbalize its thinking and just spits it back out.
	return $input['thoughts'];
},

We are still using non-reasoning models for some tasks and when we need to add reasoning, we just add this incredible ability and the output improves. I added it as a joke and it just… works 🤷

Prompt engineering is really context engineering

We discussed tools and agents, but we also have a spin on prompt management. All our prompts run through a home-grown hydration phase where a templating engine that injects dynamic context into prompt templates. This can be:

The context gets lazy-loaded at execution time. Every time we encounter [[site.current_site.title]], we retrieve the current title from the database and so on.

That way we can focus on fine-tuning prompts – for example our support chatbot has immutable configurations stored in the database that our support staff keeps tweaking daily.

Orchestration

This interplay between Abilities, Context and Agents lets us orchestrate a few behaviors for the framework:

Frontend abilities are dynamically loaded and stateless.

While initiating the request to the agent, the frontend declares available frontend abilities, and the agent may choose to use them if available. That way, we can dynamically turn them on/off or make the agent adapt to the context its executed in.

We send client context to our context system

When loaded in Gutenberg canvas, Agent gets:

This gets loaded into the context system, allowing agent to have full visibility into data on the frontend, despite running in the PHP environment.

Minimizing API roundtrips

Agent by default runs as an HTTP request serving JSON-RPC data stream per A2A protocol. Each user request typically initiates a new server round-trip and:

We keep track of “Dirty entities”

Context system is integrated with wp.data entity framework. When agent is operating on a surface with “pre-save panel”, like Gutenberg canvas:

  1. Agent changes data using edit_entity ability
  2. No changes are persisted to database until save is confirmed by the user
  3. The edited entities are marked as “dirty” and their current state is sent back to the Agent on subsequent requests.
    That way:
  4. Agent thinks it has made a change on all subsequent requests but the user is yet to confirm it
  5. Context system acts as if user has made that change allowing model to execute complex multi-step queries and user having full control and preview over individual changes.

When agent is loaded in a non-interactive environment, then the changes are persisted straight to the database if permitted.

Other implementation details

On-demand Framework design

There ain’t no rules around here. We’re trying to accomplish something

Thomas Edison

Capabilities, products, needs and approaches in AI are evolving every week and it is very hard to tell where the next big idea is coming from. To reiterate my point from the beginning: the biggest challenge in AI is staying flexible enough to adapt to the torrent of changes but still provide a framework to quickly ship actual products people use. This entire framework has been built by

There are no AI architects sitting on their high chairs. There is a bunch of engineers doing messy product work and trying to not reinvent the wheel. Huge kudos to my pragmatic coworkers Emdash, Glen Davies, Stephane Thomas, Chris Blower, Ovidiu Galatan, Aaron Fas, Derek Smart, Gziolo and many, many others!

Exit mobile version