<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Dries Buytaert</title>
    <description>On digital experiences, Open Source, Open Web, Drupal, and our digital future.</description>
    <link>https://dri.es/</link>
    <atom:link href="https://dri.es/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Introducing Garry Kasparov</title>
      <link>https://dri.es/introducing-garry-kasparov</link>
      <guid>https://dri.es/introducing-garry-kasparov</guid>
      <pubDate>Tue, 20 Oct 2026 13:33:05 -0400</pubDate>
      <description><![CDATA[<p>Yesterday evening, I had the pleasure of introducing <a href="https://en.wikipedia.org/wiki/Garry_Kasparov">Garry Kasparov</a> in Antwerp, ahead of his talk on AI and human intelligence.</p>
<p>Last year, I got to <a href="https://dri.es/unexpectedly-playing-kasparov">play chess against him</a>. This time, I got to tell a room full of people why I admire him.</p>
<p>It was a memorable evening for me, and I wanted to capture it here by sharing my speech.</p>
<figure><img src="https://dri.es/files/cache/miscellaneous-2026/garry-kasparov-in-antwerp-1280w.jpg" alt="Garry Kasparov in profile, wearing a checked blazer, raising one hand as he speaks in front of a dark slatted wall." width="1280" height="854" />    <figcaption>Garry Kasparov on AI and human intelligence. A year after losing to him in twenty-five moves, I got to introduce him instead. © Freek Swinnen.</figcaption>
  </figure>
<hr />
<p>Good evening, everyone.</p>
<p>I'm Dries Buytaert, a Belgian technologist. I created Drupal, Open Source software for building websites.</p>
<p>I've been asked to introduce Garry Kasparov and share some of my own thoughts on AI along the way.</p>
<p>Think of me as the warm-up act.</p>
<p>As I was preparing for tonight, I realized Garry and I have something in common. We both had a big year at the age of twenty-two. I released the first version of Drupal. And Garry became world chess champion.</p>
<p>My launch attracted <em>slightly</em> less attention.</p>
<p>At the time, Garry was the youngest world chess champion ever. And he spent about twenty years as the world's number one.</p>
<p>Last year, thanks to <a href="https://www.linkedin.com/in/ingegeerdens/">Inge Geerdens</a> and <a href="https://www.yournextmove.org/en">Your Next Move</a>, I got to play Garry myself. That was a dream come true. For me, he's the greatest chess player of all time.</p>
<p>He was playing against twenty of us at the same time. I knew I was going to lose. The only question was in how many moves. Garry won all twenty games, and I lasted twenty-five moves.</p>
<p>He might not remember our game, but I'll remember it for the rest of my life.</p>
<p>I played the Caro-Kann Advance Variation and was out of book after six moves.</p>
<p>Long before our game, Garry's games against a computer made headlines around the world. In 1996, Garry beat IBM's Deep Blue. But a year later, Deep Blue won the rematch.</p>
<p>For the first time in history, a computer, equipped with AI, had beaten the world chess champion in a full match.</p>
<p>Some people make history by being the first to climb Mount Everest. Others, by being the first to walk on the moon.</p>
<p>Garry made history nearly thirty years ago ... as the first knowledge worker to lose his job to AI.</p>
<p>Garry, on behalf of everyone in this room ... thank you for going first.</p>
<p>A quick show of hands: who here has wondered, &quot;What does AI mean for my job?&quot;.</p>
<p>What I really admire is what Garry did after his loss to Deep Blue. He immediately turned around, embraced AI, and explored how it could make him a better player. That takes an open mind, confidence and curiosity.</p>
<p>Today, it's our turn to rethink how we work and learn with AI. AI is changing how we compete, and what it takes to stay ahead.</p>
<p>If you run a business, you know how quickly competitors can learn from your success. In top-level chess, your games are recorded and stored in a public database. Your competitors can study them, move by move.</p>
<p>Garry had to keep improving while his competitors were learning from him. And when he retired from professional chess in 2005, he was still number one.</p>
<p>With Drupal, we compete in the open, too. Drupal has powered millions of websites around the world. Drupal is Open Source, which means anyone can use it for free, study the code, copy it, and change it. All for free.</p>
<p>Open Source democratizes access to technology. And it gives people more digital sovereignty: more control over the tools they depend on.</p>
<p>But it also means our competitors can start with the same code. We even give them the installation instructions and the manual.</p>
<p>When Garry and I played last year, we started with the same chess pieces. In exactly the same positions.</p>
<p>It turns out that is not enough to win.</p>
<p>Not for me. And not for the other nineteen players he beat.</p>
<p>Having the same tools doesn't mean you get the same results. And that is true in business, too.</p>
<p>Your competitors have access to the same AI tools you do. What matters is the expertise and judgment you bring, and how well you understand your customers.</p>
<p>And our choices about AI matter beyond business, too. AI is changing geopolitics. It's changing who holds power.</p>
<p>We've all seen those photographs of world leaders meeting the heads of technology companies. Those companies are now helping to shape national security, energy, and the economy. With that influence come questions about how we protect freedom and democracy.</p>
<p>For me, technology is at its best when it gives people more opportunity and more control over their own lives. That's what motivates me in Open Source. And it's what excites me about AI.</p>
<p>It's also why I value Garry's perspective.</p>
<p>Garry earned the world's attention through chess, then used his voice to stand up for the freedom of others, including the people of Ukraine.</p>
<p>For twelve years, Garry chaired the <a href="https://hrf.org/">Human Rights Foundation</a>. Today, he is the founder and chairman of the <a href="https://rdi.org/">Renew Democracy Initiative</a>.</p>
<p>For more than twenty years, he has stood up to Vladimir Putin and fought for a free and democratic Russia. He even tried to run for president.</p>
<p>Last year, some of us had dinner with Garry. Someone asked him what he still wanted to achieve in his life.</p>
<p>His answer has stayed with me.</p>
<p>He said he wanted to be able to visit his mother's grave in Russia.</p>
<p>Because of his opposition to Putin, he cannot safely return.</p>
<p>For Garry, freedom is deeply personal.</p>
<p>That combination of curiosity about technology and commitment to people is why I'm so glad he's here to talk about the future of AI.</p>
<p>Please join me in welcoming the thirteenth world chess champion, author, and advocate for freedom and democracy ...</p>
<p>Garry Kasparov.</p>
]]></description>
    </item>
    <item>
      <title>One description, inside and outside Drupal</title>
      <link>https://dri.es/one-description-inside-and-outside-drupal</link>
      <guid>https://dri.es/one-description-inside-and-outside-drupal</guid>
      <pubDate>Thu, 15 Oct 2026 08:26:16 -0400</pubDate>
      <description><![CDATA[<p>My website has a custom photo album module that I've maintained for about twenty years. It helps me manage more than ten thousand photos. In <a href="https://dri.es/state-of-drupal-presentation-september-2026">my DrupalCon Rotterdam keynote</a>, I talked about making that module work with AI assistants. You can <a href="https://www.youtube.com/watch?v=jrXnvVD8ryo&amp;t=2361s">watch that part of the keynote</a>. I didn't have time on stage to go into the details, so I'm doing that in this post.</p>
<p>To let assistants find and edit my photos, I used the contributed <a href="https://www.drupal.org/project/tool">Tool module</a>, which provides Tool API, to expose my module's operations as tools. These include finding photos and updating a photo's caption or alt text.</p>
<p>Most of these tools are public methods on <a href="https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/services-and-dependency-injection-in-drupal">Drupal services</a> I already had, like the one that searches my photos. A few PHP attributes are usually enough for Tool API to expose an existing method, so AI assistants can call it over MCP, the <a href="https://modelcontextprotocol.io/">Model Context Protocol</a>.</p>
<p>To describe a tool to other software, Tool API needs to know what data it accepts and returns: what each value is called, what type it is and what it means.</p>
<p>My album module doesn't store photos as Drupal entities (I know, I know), so Drupal has no field definitions for them. Instead, it keeps them in its own database table and loads each photo into an instance of <code>Image</code>, a plain PHP class. I added PHP attributes to the getters for the properties I wanted to expose, like this one for the alt text:</p>
<pre><code class="language-php">#[ToolProperty(description: 'Plain text describing the image for screen readers.', media_type: 'text/plain')]
public function getAlt(): string { 
  ... 
}
</code></pre>
<p>From attributes like this one, Tool API generates JSON Schema, a format for describing JSON data. MCP and <a href="https://www.openapis.org/what-is-openapi">OpenAPI</a>, a standard for describing HTTP APIs, both use this format. So do Drupal's <a href="https://www.drupal.org/docs/develop/theming-drupal/using-single-directory-components">Single Directory Components</a>, which use it to describe the data passed to reusable interface components.</p>
<p>In my album module, I use the same descriptions of my photo data to tell AI assistants what my tools return, generate TypeScript types for a React app that searches my photos, and define the inputs for my Drupal components.</p>
<p>The same tools also connect my site to various workflow systems. A workflow built with <a href="https://www.drupal.org/project/eca">ECA</a>, <a href="https://www.drupal.org/project/flowdrop">FlowDrop</a>, <a href="https://www.drupal.org/project/maestro">Maestro</a>, <a href="https://n8n.io">n8n</a> or <a href="https://www.activepieces.com">Activepieces</a> can call the same photo-search tool an AI assistant calls, without requiring a separate integration with my album module.</p>
<p>A module maintainer exposes an operation once, and several kinds of software can use it.</p>
<svg class="diagram" viewBox="0 0 640 344" font-size="13" text-anchor="middle" role="img" aria-label="Image.php describes a photo once in PHP attributes. Tool API generates JSON Schema from them, which AI assistants, front ends, workflow systems and Drupal components all use.">
  <style>
  .diagram rect { fill: var(--background); stroke: var(--hair-strong); }
  .diagram .source rect { fill: var(--tint); }
  .diagram path { fill: none; stroke: var(--meta); }
  .diagram marker path { fill: var(--meta); stroke: none; }
  .diagram text { fill: var(--ink); }
  .diagram .title { font-size: 16px; }
  .diagram .examples { fill: var(--meta); font-size: 12px; }
</style>
  <marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto">
  <path d="M0 0 10 5 0 10z"/>
</marker>
  <g class="source">
  <rect x=".5" y="21.5" width="180" height="74" rx="6"/>
  <text x="90.5" y="46" class="title">Image.php</text>
  <text x="90.5" y="66">Describes a photo once,<tspan x="90.5" dy="16">in PHP attributes</tspan></text>
  <rect x=".5" y="135.5" width="180" height="74" rx="6"/>
  <text x="90.5" y="160" class="title">Tool API</text>
  <text x="90.5" y="180">Generates JSON Schema<tspan x="90.5" dy="16">from those attributes</tspan></text>
</g>
  <g marker-end="url(#arrow)">
  <path d="M90.5 95.5V135"/>
  <path d="M180.5 172.5 250 37.5"/>
  <path d="M180.5 172.5 250 127.5"/>
  <path d="M180.5 172.5 250 217.5"/>
  <path d="M180.5 172.5 250 307.5"/>
</g>
  <rect x="250.5" y=".5" width="389" height="74" rx="6"/>
  <text x="445" y="25" class="title">AI assistants</text>
  <text x="445" y="45">Find, read and edit my photos over MCP</text>
  <text x="445" y="63" class="examples">Claude, or any other MCP client</text>
  <rect x="250.5" y="90.5" width="389" height="74" rx="6"/>
  <text x="445" y="115" class="title">Front ends</text>
  <text x="445" y="135">Search my photos with TypeScript types from OpenAPI</text>
  <text x="445" y="153" class="examples">React, Next.js, mobile apps</text>
  <rect x="250.5" y="180.5" width="389" height="74" rx="6"/>
  <text x="445" y="205" class="title">Workflow systems</text>
  <text x="445" y="225">Search my photos in workflows built without code</text>
  <text x="445" y="243" class="examples">ECA, FlowDrop, Maestro, Activepieces</text>
  <rect x="250.5" y="270.5" width="389" height="74" rx="6"/>
  <text x="445" y="295" class="title">Drupal components</text>
  <text x="445" y="315">Render my photos with props from a generated schema.json</text>
  <text x="445" y="333" class="examples">Single Directory Components, Drupal Canvas</text>
</svg><p />
<p>This is why I said in my keynote that I believe the <a href="https://www.drupal.org/project/tool">Tool module</a>, built by <a href="https://www.drupal.org/u/michaellander">Michael Lander</a> and team, is one of the most important modules in Drupal right now. A module's capabilities can become available to assistants, front ends and workflows without its maintainer having to build for each one separately.</p>
<p>I think every Drupal module should work this way, which is why I featured it in my keynote and why I launched a broader effort around this called Rosetta.</p>
<p>The goal is for Drupal's own systems and software outside Drupal to work from shared descriptions of data and operations. Below, I'll walk through what I built and tested with my album module.</p>
<h2>Drupal needs shared descriptions of data and operations</h2>
<p>More of the work on a site happens outside Drupal every year: in AI assistants, in front ends built with JavaScript frameworks, and in <a href="https://dri.es/drupal-role-in-agentic-workflows">orchestration platforms that connect Drupal to other systems</a>. As I explained in <a href="https://dri.es/ai-and-the-great-cms-unbundling">AI and the great CMS unbundling</a>, those systems still need the content management, governance and observability that Drupal already provides.</p>
<p>Drupal has entities, fields, permissions, revisions, workflows, content moderation and tens of thousands of contributed modules. But making a module's capabilities available to an assistant, a workflow or a front end still too often requires some integration work.</p>
<figure><img src="https://dri.es/files/cache/drupalcon-rotterdam-2026/drupal-capabilities-access-still-too-hard-1280w.png" alt="Keynote slide titled &quot;The capabilities are here, but access is still too hard&quot;." width="1280" height="720" />    <figcaption>A slide from <a href="https://dri.es/state-of-drupal-presentation-september-2026">my DrupalCon Rotterdam 2026 keynote</a>: Drupal has the capabilities, but making them available to other software is still too hard.</figcaption>
  </figure>
<p>Installing a Drupal module should make its capabilities available to software already connected to a site: new tools for assistants, new operations for workflows, and new capabilities for front-end developers.</p>
<p>In my album module, I should only have to describe a photo once and implement photo search once, not write a separate integration for every assistant, front end or workflow system.</p>
<p>In Drupal, we already know why shared descriptions matter. When a site builder adds a field to a content type, Drupal stores a definition for it: its type, its label and whether it's required. Content editing forms, Views and JSON:API all reuse that definition.</p>
<p>CCK, Drupal's Content Construction Kit, let site builders define a field once, and Drupal used that definition for the edit form, the display and the database. Views let them list, filter and sort data from any module that described its data. Rosetta is about extending that kind of reuse to more of a module's data and operations.</p>
<p class="pullquote">Shared descriptions of data and operations could be Drupal's next CCK and Views moment.</p>
<p>User interfaces also keep changing: web pages, then native mobile apps, then headless front ends, then chat, and now AI assistants and agents. I don't know what comes after agents. But if modules describe their data and operations, new interfaces can build on the capabilities we already have. The next interface reads the description and gets to work. That won't make Drupal immune to change, but it makes us fast to adapt, and fast to adapt helps us win.</p>
<p>The question then becomes how to make that practical for thousands of module maintainers. Tool API gives us a starting point, alongside years of work on Typed Data, Single Directory Components, JSON:API, Drupal Canvas Code Components, <a href="https://www.drupal.org/project/modeler_api">Modeler API</a>, and more. We need the people behind that work to help shape the approach, including whether Tool API, or parts of it, belongs in Drupal Core.</p>
<p>That's why I'm organizing the Rosetta Sprint: a few days for the people behind these projects to get together in person to compare the existing approaches, identify what is missing and agree on next steps with the relevant maintainers and Core committers.</p>
<h2>Describe existing data and expose functions as tools</h2>
<p>Tool API supports two ways to define a tool: (1) you can write a dedicated plugin class that Drupal discovers as a tool, or (2) add PHP attributes to an existing public method on a class registered as a <a href="https://www.drupal.org/docs/drupal-apis/services-and-dependency-injection/structure-of-a-service-file">Drupal service</a>.</p>
<p>I started with a dedicated plugin class but ultimately switched to PHP attributes. Attribute support was still experimental when I started, and I helped improve Tool API as I used it (<a href="https://git.drupalcode.org/project/tool/-/work_items/3583030">#3583030</a>, <a href="https://git.drupalcode.org/project/tool/-/work_items/3583031">#3583031</a>, <a href="https://git.drupalcode.org/project/tool/-/work_items/3583032">#3583032</a>, <a href="https://git.drupalcode.org/project/tool/-/work_items/3583041">#3583041</a>, <a href="https://git.drupalcode.org/project/tool/-/work_items/3583085">#3583085</a>). The <a href="https://git.drupalcode.org/project/tool/-/work_items/3518120">attribute support is now merged and available in Tool API's development version</a>. Dedicated plugin classes remain fully supported.</p>
<p>I'll show the basic pieces here rather than walk through a full tutorial. Here is how the method that searches my photos becomes the <code>find_images</code> tool, simplified a little:</p>
<pre><code class="language-php">/**
 * Searches the images.
 *
 * @param string $query
 *   Words to look for in the title, caption or alt text.
 *
 * @return \Drupal\album\Image[]
 *   The matching images.
 */
#[ToolMethod(
  id: 'find_images',
  label: 'Find images',
  description: &quot;Searches the site's images. …&quot;,
  operation: ToolOperation::Read,
  permission: 'access content',
)]
public function search(string $query): array {
  // The search code is unchanged.
}
</code></pre>
<p>The method body stays the same: it takes a search query and returns the matching images. The <code>#[ToolMethod]</code> attribute gives the tool its ID, label and description. It also declares that the operation reads data and that the caller needs the <code>access content</code> permission. The search code must still enforce any restrictions on which photos the caller can see.</p>
<p>Tool API reads the input's name and type, <code>query</code> and <code>string</code>, from the method signature. When type information is missing, Tool API tries to infer it from the method signature and docblock. Here, the <code>Image[]</code> return annotation tells Tool API that the array contains <code>Image</code> objects.</p>
<p>Knowing that the result is a list of <code>Image</code> objects doesn't tell other software which properties a photo has. I describe those with <code>#[ToolProperty]</code> attributes on the <code>Image</code> class's getters, like <code>getAlt()</code> above.</p>
<p>The attributes also explain what each property means. In PHP, a title, a caption and a URL can all be strings, but other software needs to know which is plain text, which contains HTML and which is a URL. I illustrated that distinction in the keynote:</p>
<div class="large">
<figure><img src="https://dri.es/files/cache/drupalcon-rotterdam-2026/image-class-tool-attributes-1280w.png" alt="Keynote slide titled &quot;Three strings can mean three different things&quot;." width="1280" height="720" />    <figcaption>A slide from <a href="https://dri.es/state-of-drupal-presentation-september-2026">my DrupalCon Rotterdam 2026 keynote</a>: all three values have PHP's <code>string</code> type, but <code>#[ToolProperty]</code> attributes describe the title as plain text, the caption as HTML and the URL as a URI.</figcaption>
  </figure>
</div>
<p>Tool API only includes the photo properties I explicitly describe with <code>#[ToolProperty]</code>. The attribute can go on a public class property or a getter method. On <code>getUrl()</code>, it exposes the returned value as a photo property named <code>url</code>; it doesn't make the getter a callable tool.</p>
<p>Under the hood, Tool API uses <a href="https://www.drupal.org/docs/develop/drupal-apis/typed-data-api/typed-data-api-overview">Drupal's Typed Data API</a> to represent these descriptions and can generate JSON Schema from them.</p>
<p>Drush, MCP, ECA, FlowDrop, Maestro and my album module's custom HTTP route can all call the same runner, <code>ToolRunner::run()</code>. That means the same permission checks and input validation apply whichever way a tool is called. The runner also distinguishes invalid input from denied access or an execution failure, so each integration doesn't have to work that out for itself.</p>
<p>As mentioned, PHP attributes aren't the only way to define a tool. A dedicated plugin class is a bit more verbose, but it has some advantages. For example, it doesn't have to live in the module it exposes. A separate module can define the tools and call the original module's services and APIs, with no changes to its code. <a href="https://www.drupal.org/project/tool_belt">Tool Belt</a> currently takes that separate-module approach for Drupal Core operations, like creating content and updating fields. That means we can add tool support for existing modules without waiting for their maintainers to adopt Tool API.</p>
<p>If you maintain a module, try exposing one useful operation as a tool. If you get stuck, or Tool API can't describe the data you need, <a href="https://www.drupal.org/project/issues/tool">open an issue</a>. Your feedback can help us improve Tool API before its first stable 1.x release. Tool API is still young, so you can help shape it.</p>
<h2>AI assistants can handle complex tasks without developers</h2>
<p>I actually started with search. A while ago, I exposed my blog's search to AI agents and wrote about it several times: as an <a href="https://dri.es/helping-agents-discover-my-site-search-with-mcp">MCP tool</a>, through an <a href="https://dri.es/helping-agents-discover-my-site-search-with-an-api-catalog">API catalog</a> and with <a href="https://dri.es/helping-agents-discover-my-site-search-with-agentic-resource-discovery">Agentic Resource Discovery</a>. That code started out custom, but I've since updated it to use Tool API as well.</p>
<p>Later, I added the album tools. My album module provides seven. The ones I use most are <code>find_images</code> and <code>get_image</code>, which find and read photos, and <code>update_image</code>, which changes a photo's title, caption or alt text.</p>
<p>I've used these tools to write titles and alt text for whole albums. Here are a few other things I ask an assistant to do:</p>
<ul>
<li>Find every photo that still has no alt text, across all albums, and suggest alt text for me to review.</li>
<li>Find typos or grammar issues in my captions.</li>
<li>List the photos in an album that have no title, and suggest titles for me to review.</li>
</ul>
<p>The album tools and <code>find_posts</code> often work together. If I have an album from a trip and a blog post about the same trip, an AI assistant can call <code>find_posts</code> while it writes alt text for the album's photos, and use the post as context. It picks up details such as where we went, who was there and what the trip was for. The alt text gets better, without me having to point the assistant at the post.</p>
<p>My album module's admin screens can't do any of these tasks quickly, and some tasks have no screen at all. Before these tools, I either had to run SQL queries on the database or didn't do the task at all. To fix missing alt text, I'd query for the photos without it, then open each photo's edit screen in a browser. Updating dozens of images that way gets tedious.</p>
<p>Now an assistant finds the photos and drafts the alt text, and I review and approve its suggestions in Claude. It's at least 10 times faster than my old workflow.</p>
<p>I've been writing SQL for nearly 30 years, so I could always do these jobs myself. Many site owners can't. They ask a developer for help, or the job never gets done. Tools can remove that dependency. Photos are just my example. Any operation a module exposes as a tool becomes something a site owner can ask for directly, without waiting for a developer.</p>
<h2>AI assistants can handle complex tasks without dedicated UIs</h2>
<p>When an assistant calls <code>get_image</code>, it gets the photo's properties and the photo itself, so it can see what it's describing. In Claude, an interface I built also displays the photo in the chat. It uses <a href="https://modelcontextprotocol.io/extensions/apps/overview">MCP Apps</a>, an MCP extension that lets a server provide an HTML interface the client renders in the conversation.</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/z_l4ZYLCCOw" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>Every module has tasks that are too rare or too specific to justify a screen of their own. A maintainer can build screens for everyday work and make rare or specialized tasks available through tools. I've already removed parts of my album module's interface, like the button that generated alt text with a language model, because an assistant now does that job. I expect to remove more.</p>
<p>Shared descriptions can also make it easier to tailor interfaces to the people using them. Tool API can already generate a form from a tool's inputs and apply the same validation constraints when the tool is called without a form. Michael Lander is exploring this further in his <a href="https://github.com/mlander/data_surface">Data Surface prototype</a>, where forms and tools use the same definitions of accepted values and validation rules.</p>
<p>An organization could build admin screens around its own workflow while reusing those definitions and validation rules. With the right prompting, assistants can already build interfaces around these tools. For a batch of photo updates, that could be a table of images and proposed alt text where I can review and approve the changes. Over time, I expect assistants to recognize when an interface would help with a task and build one without being asked.</p>
<p>Michael's work builds on Drupal's configuration validation, and it is a good example of what I mean when I say <a href="https://dri.es/why-drupal-is-built-for-the-ai-era">Drupal has an accidental AI advantage</a>. Wim Leers began <a href="https://wimleers.com/talk/config-validation">advocating for configuration validation</a> in 2022. AI wasn't part of his case. Years later, Michael can reuse those validation constraints to power forms and tools from one definition, and in <a href="https://wimleers.com/validation-first">Validation-first</a>, Wim describes how the same precision helps AI tools generate valid Drupal Canvas code components.</p>
<h2>JavaScript front ends can use the same tools as AI assistants</h2>
<p>But that is not all. With my album data and operations described, I decided to make the same data and tools available to front-end developers. I wrote two pieces of connecting code: a shared HTTP route that calls read-only tools, and a generator that produces an OpenAPI document from their definitions. Together, they take under 150 lines of PHP.</p>
<p>The route uses the URL to identify the tool and the query string to supply its arguments. For example, a request to <a href="https://dri.es/api/find-images?query=ski"><code>/api/find-images?query=ski</code></a> calls <code>find_images</code> with <code>ski</code> as its query and returns the result as JSON. I didn't write another photo search or a separate route for each tool.</p>
<p>The auto-generated OpenAPI document, available at <a href="https://dri.es/openapi.json">https://dri.es/openapi.json</a>, describes those endpoints, their parameters and their responses. It uses the same <code>#[ToolMethod]</code> and <code>#[ToolProperty]</code> attributes the MCP tools use.</p>
<p>OpenAPI is a 15-year-old standard for describing HTTP APIs, so it's supported by many developer tools. <a href="https://swagger.io/tools/swagger-ui/">Swagger UI</a>, for example, lists my endpoints and lets you try them. This video shows the document, then Swagger UI reading it and calling the API for &quot;driesnote&quot;:</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/lFNiD6nlgp0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>Many front-end developers use TypeScript, which adds static types to JavaScript. A type can describe an image as an object with a string <code>alt</code> and a string <code>title</code>. The TypeScript compiler checks the code against those types before it runs, and editors like <a href="https://en.wikipedia.org/wiki/Visual_Studio_Code">VS Code</a> show type errors as you write.</p>
<p>To see what this gives a front-end developer, I asked Claude Code to build a small React app that searches my photos from the browser. The app knows nothing about Drupal, and building it didn't require any Drupal knowledge either. All Claude Code had was my site's URL and its generated OpenAPI document.</p>
<p>I'm not a front-end developer, so it was interesting to watch. Claude used a code generator called <a href="https://openapi-ts.dev/introduction">openapi-typescript</a> to turn my OpenAPI document into TypeScript types in under a second. A small library called <a href="https://openapi-ts.dev/openapi-fetch/">openapi-fetch</a> then uses those types, so the compiler can check the request paths and parameters in the app's code.</p>
<p>The whole app is a single React component of 45 lines, and I wrote no additional Drupal code for it. In this video, it searches for &quot;driesnote&quot;:</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/dPzySZ6AONc" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>The HTTP route and OpenAPI generator are prototype code on my site. Nothing in them is specific to photos, so they shouldn't be something each site or module has to write. They belong in shared infrastructure that works for any module's tools. JSON-RPC is one place that could happen: if <a href="https://www.drupal.org/project/jsonrpc">JSON-RPC</a> supported Tool API, it could expose tools from other modules without each module writing its own JSON-RPC integration.</p>
<h2>Workflows can call module operations as tools</h2>
<p>Inside Drupal, <a href="https://www.drupal.org/project/eca">ECA</a>, <a href="https://www.drupal.org/project/flowdrop">FlowDrop</a> and <a href="https://www.drupal.org/project/maestro">Maestro</a> can call tools as steps in a workflow. The <a href="https://www.drupal.org/project/eca_tool">ECA Tool</a> module, for example, makes tools available as ECA actions.</p>
<p>In the keynote, Shibin Das showed a FlowDrop workflow he built with my album module's tools. It searches my photos and lists my albums, first without any AI, then with an agent that decides which tools to use:</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/7CnZd63-JWg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>Workflow systems can also expose tools of their own. ECA calls its workflows &quot;models&quot;, and the ECA Tool module exposes each Tool event in a model as a separate tool. One model can therefore offer several tools. Maestro can expose public workflow templates as tools, using their variables as inputs.</p>
<p>A workflow that is also a tool can be used like any other tool. A Maestro workflow can call a tool exposed by ECA, through the same Tool API integration it uses to call my photo search. Neither system needed a direct integration with the other. I still find that a little hard to get my head around, but I love it and see the potential.</p>
<p>Automation platforms outside Drupal need a way to call those tools too. Last year, Jürgen Haas and I built the <a href="https://www.drupal.org/project/orchestration">Orchestration module</a> to connect Drupal with Activepieces. It lets Activepieces call tools defined through Tool API, run AI agents and trigger ECA models, while events in Drupal can trigger workflows in Activepieces. I described that setup in <a href="https://dri.es/connecting-drupal-with-activepieces">Connecting Drupal with Activepieces</a>. Other platforms can call Drupal's tools over MCP, for example through <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-langchain.mcpClient">n8n's MCP Client node</a>.</p>
<p>Tools compose. An assistant can combine them in a single task, a workflow can chain them, and a workflow can be one of the tools in another workflow. Once a system can call Drupal's tools, it can reuse that connection for operations from different modules.</p>
<p>That is the <a href="https://dri.es/the-orchestration-shift">orchestration shift</a>: a stack becomes a network, and workflows coordinate work across applications. Drupal can be a node in that network, with its modules offering operations that other systems can call. For <a href="https://dri.es/ai-and-the-great-digital-agency-unbundling">agencies building those workflows</a>, Tool API makes Drupal's capabilities available as reusable steps.</p>
<h2>Drupal components can share data descriptions</h2>
<p>So shared descriptions help JavaScript front ends and other applications use Drupal's data, but they can also help Drupal's own rendering system, even when no AI assistant or external application is involved.</p>
<p>My photos are rendered using <a href="https://www.drupal.org/docs/develop/theming-drupal/using-single-directory-components">Single Directory Components</a>, or SDCs. Each component keeps its <a href="https://twig.symfony.com/">Twig template</a>, CSS and metadata in one folder. Its <code>.component.yml</code> file declares the component's inputs, called props, and describes the data they accept.</p>
<p>My album module already describes a photo's properties with <code>#[ToolProperty]</code> attributes. I was also maintaining descriptions of those same properties in each photo component's <code>.component.yml</code> file. When I changed what a photo property contained, I had to remember to update both descriptions.</p>
<p>So I wrote a <a href="https://www.drush.org">Drush</a> command that reads the PHP attributes and generates a shared <code>schema.json</code> file. My album module's <a href="https://dri.es/modules/custom/album/schema.json">generated photo schema</a> describes properties such as <code>alt</code> and <code>caption</code>: their types and what they contain. Each photo component's <code>.component.yml</code> file now references that shared description instead of describing those properties again. When I change what a photo property contains, I update its PHP attribute and run the command.</p>
<p>Moving the descriptions into a shared file creates a new problem. Anything that reads a component's <code>.component.yml</code> now finds a reference to <code>schema.json</code>, not the property definitions. A tool that builds a component catalog, showing what data each component expects, has to find and resolve that reference itself. Core's component API doesn't do it for them: it returns the reference without the photo's property definitions.</p>
<p>Pierre Dureau's merge request in <a href="https://www.drupal.org/project/drupal/issues/3352063">Core issue 3352063</a> addresses that gap, though it isn't merged yet. With it applied on my site, Core's component API followed the reference and loaded the photo property definitions, without any changes to the schema file.</p>
<p>That matters for generative UI. Frameworks like <a href="https://www.openui.com">OpenUI</a> and <a href="https://a2ui.org">A2UI</a> let an AI agent assemble an interface from a catalog of components, so they need to know which components exist and what props each one accepts. With this change, an integration could build that catalog from Drupal's API instead of writing its own reference resolver. Tools that read the component files directly still need to learn to follow these references. The SDC add-on for <a href="https://storybook.js.org/docs">Storybook</a>, a tool for developing and previewing components, doesn't yet follow the Drupal-specific references I use.</p>
<p>The same goes for the generator. Other module maintainers shouldn't each have to write one like mine. My experiment uses Tool API's property descriptions, but describing data and generating schemas are useful beyond tools. Some of that support could belong in Drupal Core, benefiting both SDC and the <a href="https://www.drupal.org/docs/develop/drupal-apis/icon-api">Icon API</a>, which uses JSON Schema to describe icon settings such as size and color.</p>
<p>My approach only covers components built to render my module's photos. General-purpose components define their own inputs and still need a way to get Drupal's data. Drupal describes entity fields with <a href="https://www.drupal.org/node/1794140">Typed Data</a>, while SDC describes props with JSON Schema. <a href="https://www.drupal.org/project/ui_patterns">UI Patterns</a> already handles much of the work between them, including which data sources can supply a prop. Rosetta should build on that work too.</p>
<h2>Make shared descriptions standard practice</h2>
<p>The same photo descriptions and operations now support AI assistants, a React app, workflows inside and outside Drupal, and Drupal components.</p>
<p>I want more module maintainers to adopt Tool API: describe their modules' data once and expose useful operations as tools. That should become a normal part of developing and maintaining a Drupal module.</p>
<p>We should also provide the HTTP integration, OpenAPI generator and component schema generator as shared Drupal infrastructure. Module maintainers shouldn't each have to write that connecting code.</p>
<p>My prototypes show this working for one module. Rosetta is about making that reuse work across Drupal. We need to work with the relevant maintainers and Core committers to decide what belongs in Tool API, its integrations and Drupal Core.</p>
<p>If you maintain a module, try exposing one useful operation as a tool and using it with another system. Share what worked, and <a href="https://www.drupal.org/project/issues/tool">open an issue</a> when you find something Tool API can't describe or do.</p>
<p><em>Thanks to <a href="https://www.drupal.org/u/yautja_cetanu">Jamie Abrahams</a>, <a href="https://www.drupal.org/u/e0ipso">Mateu Aguiló Bosch</a>, <a href="https://www.drupal.org/u/ronaldtebrake">Ronald te Brake</a>, <a href="https://www.drupal.org/u/d34dman">Shibin Das</a>, <a href="https://www.drupal.org/u/pdureau">Pierre Dureau</a>, <a href="https://www.drupal.org/u/mglaman">Matt Glaman</a>, <a href="https://www.drupal.org/u/jurgenhaas">Jürgen Haas</a>, <a href="https://www.drupal.org/u/marcus_johansson">Marcus Johansson</a>, <a href="https://www.drupal.org/u/_randy">Randy Kolenko</a>, <a href="https://www.drupal.org/u/michaellander">Michael Lander</a>, and <a href="https://www.drupal.org/u/f0ns">Fons Vandamme</a> for reviewing this post.</em></p>
]]></description>
    </item>
    <item>
      <title>State of Drupal presentation (September 2026)</title>
      <link>https://dri.es/state-of-drupal-presentation-september-2026</link>
      <guid>https://dri.es/state-of-drupal-presentation-september-2026</guid>
      <pubDate>Wed, 07 Oct 2026 03:50:03 -0400</pubDate>
      <description><![CDATA[<p>Drupal is now light-years ahead of its reputation. Closing that gap is our #1 challenge, and it was the main message of my DrupalCon Rotterdam keynote.</p>
<p>Just over two years ago, I launched <a href="https://dri.es/introducing-drupal-starshot-product-strategy">Drupal Starshot</a>. I did it because I wasn't sure we could still innovate like we used to. It turns out we can.</p>
<p>Starshot became <a href="https://dri.es/drupal-cms-1-released">Drupal CMS</a>, which brings together Site Templates, Recipes, Drupal AI, Drupal Canvas and more to make building websites easier.</p>
<p>Alongside that work, Drupal Core has become a faster and better framework for developers. A stronger Drupal Core helps us build a better Drupal CMS, while building Drupal CMS has driven further improvements in Drupal Core.</p>
<p>Unfortunately, many people outside the Drupal community haven't seen what Drupal can do today. In Rotterdam, I showed how far we have come and where we're going next, and we launched an advocacy program to help more people see how great Drupal has become.</p>
<p>If you missed the keynote, you can <a href="https://youtu.be/jrXnvVD8ryo">watch the video below</a> or <a href="https://dri.es/files/state-of-drupal-september-2026.pdf">download my slides</a> (86 MB).</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/jrXnvVD8ryo" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<h2>More ways to reach more people</h2>
<p>The first part of my keynote showed improvements for multilingual sites, JavaScript front-ends, headless Canvas, and Drupal AI.</p>
<p>Drupal has long had strong multilingual capabilities, but <a href="https://www.drupal.org/project/canvas">Drupal Canvas</a>, our powerful new page builder, didn't yet support multilingual sites. Not only did we add multilingual support to Canvas, but we made all multilingual sites easier to set up.</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/xoPDb4UhHyg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure> 
<p>React has one of the world's largest developer communities. Drupal Canvas code components are written in React, and we designed the developer experience around the tools, patterns, and workflows front-end JavaScript developers already know. They can use their preferred development tools, including modern AI-assisted workflows, without having to learn Drupal-specific concepts or conventions. We've continued to make that experience better, lowering the barrier for a much larger community of developers to build with Drupal.</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/zim-Wr-HJNk" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>At DrupalCon, we extended that same model with <a href="https://project.pages.drupalcode.org/canvas/headless/">Drupal Canvas Headless</a>. Developers can now use Drupal to manage content while building the front-end separately with frameworks like Next.js, Astro, TanStack, Angular, or Nuxt. Editors still get the visual experience of Drupal Canvas: they can build pages in Drupal and preview exactly how those pages will appear on the front-end.</p>
<p>This changes an important trade-off. Teams no longer have to choose between a modern JavaScript front-end and Drupal Canvas' visual authoring experience. They can have both. That opens Drupal to more developers, more front-end architectures, and more types of projects.</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/IPaNNc0MBnE" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>Drupal AI is moving so fast that I could have filled a whole keynote with it. Instead, we launched a <a href="https://drupal.org/ai/demo">new Drupal AI demo</a> where people can experience Drupal AI for themselves. It comes preconfigured, making it easy to explore what Drupal AI can do or even show it to customers.</p>
<p>With the latest Drupal AI, you can ask questions and get answers grounded in your site's content. You can audit your content against brand guidelines, translate content, classify content,  build pages and React components with AI, and more. Through MCP, AI assistants can work with Drupal content from outside Drupal.</p>
<p>What excites me most is the foundation underneath all of this. Drupal AI turns Drupal into an AI harness: organizations can connect AI models to structured content, tools, and workflows, while keeping control through permissions, guardrails, observability, metering, and a choice of AI models.</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/Ka1cLG5nvkw" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<h2>Opening Drupal's capabilities to other software</h2>
<p>The second part of my keynote looked at another important shift: AI assistants can give people a new way to work with Drupal.</p>
<p>In one demo, an editor simply asked an AI assistant to unpublish a page. They didn't need to know where to click or understand Drupal's revisions, moderation workflows, or permissions. The assistant translated their intent into action, but Drupal remained in control: it checked whether the editor was allowed to unpublish the page and applied the site's publishing rules, just as it would if they had done it by hand.</p>
<p>If you want to see the demo in more detail, Scott Falconer, who worked on it, recorded a <a href="https://www.youtube.com/watch?v=Gpq0ZnMg4mg">behind-the-scenes walkthrough</a> showing how to set it up yourself, what happens under the hood, and how to get involved.</p>
<p>That demo was built on the <a href="https://www.drupal.org/project/tool">Tool module</a>, which I believe is one of the most important modules for Drupal developers to watch. Drupal modules already contain thousands of useful capabilities: publishing content, managing users, processing media, changing configuration, and much more. The Tool module gives developers a standard way to expose those capabilities so AI assistants and other software can discover and use them.</p>
<p>Today, exposing existing capabilities as tools can still require too much code. My own album module didn't expose tools, and making its existing capabilities available to an AI assistant took about 1,000 additional lines of code.</p>
<p>But once those tools were available, the value became obvious. Adding MCP support has already changed how I manage the more than 10,000 photos on my site. Tasks that used to take a lot of time can now be done much faster and more accurately with an AI assistant, while Drupal still manages the content, permissions, and workflows underneath.</p>
<p>That experience reinforced something I wrote about in <a href="https://dri.es/ai-and-the-great-cms-unbundling">AI and the great CMS unbundling</a>: AI can take on more of the execution work while Drupal remains the control layer. I expect more organizations will want to manage parts of their sites through AI assistants. AI can make complex tasks much faster and easier without giving up the structure, governance, and safeguards that Drupal provides.</p>
<p>I believe thousands of module developers will eventually want to expose their capabilities in the same way, so I've been working with Matt Glaman to make it much easier. Matt has been developing <a href="https://git.drupalcode.org/project/tool/-/work_items/3518120">a change to the Tool API</a> that lets developers expose existing PHP methods as tools using attributes.</p>
<p>The idea is simple: add PHP attributes to an existing method to describe its name, purpose, inputs and outputs. In my album module, roughly 1,000 lines of integration code became about 20 attributes. My site already uses the experimental branch, and we're working to get it merged into the <a href="https://www.drupal.org/project/tool">Tool module</a> proper.</p>
<p>And this isn't only about AI. The same tools can be called by AI assistants over MCP, by other applications over HTTP, or used to generate schemas for JavaScript components and connect Drupal modules to workflow systems like <a href="https://www.drupal.org/project/eca">ECA</a>, <a href="https://www.drupal.org/project/flowdrop">FlowDrop</a>, and <a href="https://www.drupal.org/project/maestro">Maestro</a>. The video below shows FlowDrop using capabilities exposed by my album module:</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/7CnZd63-JWg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<h2>Talk louder about Drupal</h2>
<p>In the last part of my keynote, I came back to Drupal's reputation gap. We can make enormous progress with Drupal, but that doesn't automatically change what people or AI agents think of it. For example, an <a href="https://dri.es/do-ai-coding-agents-recommend-drupal-2026">AI coding agent I tested in June</a> didn't even mention Drupal CMS or Site Templates when it evaluated Drupal.</p>
<p>Over the past few years, much of my personal focus has been on accelerating innovation in Drupal. That work is paying off, and we have real product momentum. I'm now shifting more of my attention to the other side of the equation: making sure people see what we've built, understand what has changed, and know why it matters.</p>
<figure><img src="https://dri.es/files/cache/drupalcon-rotterdam-2026/reputation-1280w.png" alt="Slide reading &quot;Drupal is now light-years ahead of its reputation&quot; over translucent red and yellow shapes, with a pink glass rocket on the left." width="1280" height="720" />  </figure>
<p>So I announced the <a href="https://www.drupal.org/advocacy">Drupal Advocacy Program</a>, led by the Drupal Association.</p>
<p>Drupal already has a contribution credit system. It recognizes the people who contribute to Drupal and the organizations that support their work. For businesses, those credits also help determine visibility in the Drupal marketplace. Until now, we've been much better at recognizing technical contributions and financial support than advocacy work.</p>
<p>The new Drupal Advocacy Program will change that. Advocacy work can now earn contribution credit, and work that reaches people outside the existing Drupal community can earn more weight.</p>
<p>If we want to close Drupal's reputation gap, we can't only talk to people who already know Drupal well. We need more talks at external events, articles in broader publications, customer stories, demos, tutorials, and other work that helps people see what Drupal can do today.</p>
<p>Blue Drop Labs is a good example of what I mean. The day after the DriesNote, they published a post on <a href="https://www.bluedroplabs.com/resources/articles/drupal-canvas-headless-astro">building a website with Drupal CMS and Canvas Headless</a>. The project itself was already live, but they quickly turned that work into a story others could learn from, with demos showing how editors and developers work with Drupal today.</p>
<p>That is exactly what I mean by &quot;talking louder&quot;. There is already a lot of impressive Drupal work happening. We need to do a better job of showing it to the world.</p>
<p>I was a little nervous about announcing the Advocacy Program. We've wrestled with Drupal's reputation gap for a long time, and I felt we needed to do more than acknowledge it. Using contribution credit to reward advocacy was a concrete way to change the incentives, but I wasn't sure how the community would react.</p>
<p>It turned out to be one of the best-received announcements of the keynote. That response gave me confidence that people were ready to act on the reputation gap, not just recognize it.</p>
<p>You can <a href="https://www.drupal.org/association/blog/drupal-advocacy-program">learn how to earn Drupal advocacy credit</a> and <a href="https://www.drupal.org/advocacy">submit your advocacy work</a>.</p>
<figure><img src="https://dri.es/files/cache/drupalcon-rotterdam-2026/talk-louder-1280w.png" alt="Slide reading &quot;Talk louder&quot; over translucent circuit boards and electronic parts glowing red, orange and yellow." width="1280" height="720" />  </figure>
<p>My ask is simple: talk louder about Drupal, especially to people outside our community. Drupal doesn't need hype, but we do need a better public record. We need more people showing, with real examples, what Drupal can do today.</p>
<p><em>I want to extend my gratitude to everyone who contributed to making my presentation and demos a success. A special thank you to <a href="https://www.drupal.org/u/balintbrews">Bálint Kléri</a>, <a href="https://www.drupal.org/u/breidert">Christoph Breidert</a>, <a href="https://www.drupal.org/u/g%C3%A1bor-hojtsy">Gábor Hojtsy</a>, <a href="https://www.drupal.org/u/lauriii">Lauri Timmanee</a>, <a href="https://www.drupal.org/u/mglaman">Matt Glaman</a>, <a href="https://www.drupal.org/u/michaellander">Michael Lander</a>, <a href="https://www.drupal.org/u/pameeela">Pamela Barone</a>, <a href="https://www.drupal.org/u/d34dman">Shibin Das</a>, and the <a href="https://www.drupal.org/ai/partners">Drupal AI partners</a>. Many others contributed indirectly to make this possible. If I've inadvertently omitted anyone, please reach out.</em></p>
]]></description>
    </item>
    <item>
      <title>Acquia rebrands around content and Drupal</title>
      <link>https://dri.es/acquia-rebrands-around-content-and-drupal</link>
      <guid>https://dri.es/acquia-rebrands-around-content-and-drupal</guid>
      <pubDate>Mon, 14 Sep 2026 20:13:35 -0400</pubDate>
      <description><![CDATA[<p>Today <a href="https://www.acquia.com/">Acquia</a> launched a new brand, and my favorite part is the updated logo. Right under the Acquia name, it now says &quot;Powered by Drupal&quot;.</p>
<p>Drupal has always been at the core of Acquia, but for the past 5 years it was less visible in how we described ourselves. Now it's front and center again.</p>
<div class="large">
  <figure><img src="https://dri.es/files/cache/acquia/acquia-com-september-2026-1280w.png" alt="Acquia.com homepage promoting Acquia as &quot;the agentic content platform that marketing loves and IT trusts&quot;." width="1280" height="834" />  </figure>
</div>
<p>But that is not the main reason for the rebrand. The bigger reason for the rebrand is to help people see what Acquia has become. Our products have evolved faster than awareness of them.</p>
<p>The new brand leads with content instead of digital experiences, and the homepage calls Acquia an &quot;agentic content platform&quot; rather than a &quot;digital experience platform&quot;.</p>
<p><a href="https://www.acquia.com/products/source">Acquia Source</a> is our new <em>command center</em>, bringing content management, digital asset management, and web governance into one workspace. <a href="https://www.acquia.com/products/source/ai">Acquia AI</a> coordinates agent work across those tools.</p>
<p>For agents to work safely across these tools, they need content they can trust and clear rules for using it. Somebody still has to decide what is approved, who can use it, and where it can go. I wrote about that in <a href="https://dri.es/ai-and-the-great-cms-unbundling">AI and the great CMS unbundling</a>, and the new brand puts that idea at the center of our story.</p>
<p>Drupal is well suited for that job. Structured content, granular permissions, workflows, and revision history are the things agents need to work safely, and Drupal has refined them for years.</p>
<p>Customers can use Acquia Source CMS, our <a href="https://dri.es/the-product-we-should-not-have-killed">fully managed Drupal SaaS offering</a>. For teams that want full control over their Drupal sites, we offer Acquia Cloud. Acquia Source brings sites on either platform into a shared workspace.</p>
<p>As you scroll the new homepage, it builds up our technology stack one layer at a time, starting from Drupal.</p>
<figure><div style="position: relative; padding-bottom: 56.25%; height: 0"><iframe src="https://www.youtube-nocookie.com/embed/s38IkIirDeo" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%" loading="lazy" title="YouTube video" allowfullscreen></iframe></div></figure>
<p>Acquia leaning into <a href="https://www.drupal.org/">Drupal</a> is also good news for Drupal itself. It helps close the <a href="https://dri.es/the-gap-between-drupal-and-its-reputation">gap between Drupal and its reputation</a>. Drupal is still often seen as a CMS that requires a developer for everything, even as improvements in recent years have made it easier for marketers to build pages and manage content themselves.</p>
<p>So alongside the new brand, we'll be investing more in helping the Drupal community evangelize Drupal, reaching developers and marketing leaders who may not have looked at it in years. I want more people to see what Drupal has become.</p>
]]></description>
    </item>
    <item>
      <title>The 60-second procurement test</title>
      <link>https://dri.es/the-60-second-procurement-test</link>
      <guid>https://dri.es/the-60-second-procurement-test</guid>
      <pubDate>Wed, 09 Sep 2026 14:45:17 -0400</pubDate>
      <description><![CDATA[<p>I believe any Open Source project with a commercial or institutional funding ecosystem should publish an official contribution record: who contributes, how much and over what period, what kind of work they do, which parts of the project they work on, and, where disclosed, who paid for the work.</p>
<p>The test for a good record is simple. A buyer should be able to answer three questions in about a minute: does this vendor contribute at all, how much do they contribute compared to other vendors, and do they work on the parts of the project I care about?</p>
<p>A good record serves both sides. Buyers get answers they can check, and vendors who contribute get credit for work that often goes unseen.</p>
<p>Of course, contribution does not prove that a vendor can deliver. It is one procurement factor alongside delivery capability, expertise, and price.</p>
<h2>Vendor claims are hard to verify and compare</h2>
<p>Say you need a vendor for your Drupal site, your Kubernetes cluster, or whatever Open Source software you use, and two firms bid. Both say they're deeply involved in the project, and both might be telling the truth by their own definition of &quot;involved&quot;.</p>
<p>Without a project record, verifying those claims often means reconstructing each vendor's contribution history yourself.</p>
<p>The code repository seems like the obvious place to start, but even counting commits by vendor is not simple. A project has to connect individual contributors to the organizations that employed or funded their work, and those relationships can change over time.</p>
<p>More importantly, Open Source contribution can include documentation, support, event organization, governance work, promotion, and more. Much of that work happens outside the code repository or isn't recorded in a form a buyer can easily interpret. The data may all be public, but public is not the same as usable. A contribution record exists to close that gap.</p>
<h2>Projects are best placed to decide which contributions matter</h2>
<p>Drupal has tracked contribution credits since 2015, following <a href="https://dri.es/a-method-for-giving-credit-to-organizations-that-contribute-code-to-open-source">a method I proposed in 2014</a> and now <a href="https://www.drupal.org/contribution-credit">governed by the Drupal Association</a>. Its system records more than code and gives credit not only to individual contributors, but also to the organizations and customers that fund their work.</p>
<p>Not every contribution counts the same. A contribution to Drupal Core or a strategic initiative earns more credit than one to a module that few sites use, and work on contributed projects counts for more when more sites depend on them.</p>
<p>Raw contribution data does not tell buyers which work matters most to the project. The project has the context to decide what advances its priorities and how different contributions should be weighted. Its record should publish those rules, link to the underlying contributions, and explain how mistakes are corrected.</p>
<p>In Drupal, contribution credits feed into the <a href="https://www.drupal.org/drupal-services">Drupal.org marketplace</a> where buyers can look up agencies and compare them. While we have a strong foundation, we still have work to do to make the record easier to find, understand, and compare.</p>
<h2>The record is the project's job, not the vendor's</h2>
<p>A fair objection is that this puts the burden on the wrong party: if a vendor claims to contribute, the vendor should prove it, and the project shouldn't spend scarce time tracking contributions.</p>
<p>Suppose the two firms from earlier both send proof. Each proves its own numbers, in the measure it looks best on, and it is still not clear who has the better record. Buyers meet this problem everywhere, which is why procurement leans on third parties: auditors, certifiers, industry analysts, references.</p>
<p>For vendor contribution, the project is often the natural third party. The work happens inside it, so it has the data and is uniquely placed to express an opinion on the value and strategic impact of a vendor's contribution.</p>
<p>A contribution record is a way for a project to recognize its <a href="https://dri.es/balancing-makers-and-takers-to-scale-and-sustain-open-source">Makers</a> and to say who it would like to see win bids. It also settles the question once, for every buyer, instead of every buyer auditing every bid.</p>
<p>A project should take on the burden when publishing the record is in its interest. Making contribution visible steers commercial work toward the vendors that sustain the project, and that work is what pays for the contribution. So the project gets more contribution, and public credit is how the project gives back to the vendors that contribute.</p>
<h2>Projects can start with a simple record</h2>
<p>Not every project may want or need a system as elaborate as Drupal's. How much record a project needs depends on how services around it are bought and sold: a project with hundreds of firms competing for work needs more than one with a handful of known vendors, and a project with no commercial ecosystem may not need a record at all.</p>
<p>For a small project, a text file in the official repository listing its recognized organizational contributors and what they worked on can be enough for a first version.</p>
<p>More complex ecosystems may need to categorize vendors, support searches by location, or weight different kinds of contribution. Whatever form it takes, the record has to be easy to find and quick to use.</p>
<p>So if you maintain an Open Source project with a commercial or institutional funding ecosystem, start publishing a contribution record and improve it over time.</p>
<p>If you buy services built on Open Source, ask for the project's contribution record. Decide at the start how contribution should count in your purchasing decision, then check bidder claims against the record.</p>
<p>Finally, if you fund Open Source, use the record to find contributors working on the parts of the project you want to support.</p>
<p>Where buyers and funders can consider contribution, I expect projects with clear records to see more commercial opportunities go to their maintainers than projects that rely on appeals alone.</p>
<p><em>Special thanks to <a href="https://en.wikipedia.org/wiki/Henry_Poole_(technologist)">Henry Poole</a> for his contributions to this blog post, and to <a href="https://www.linkedin.com/in/sachikomuto/">Sachiko Muto</a>, <a href="https://www.drupal.org/u/farriss">Tiffany Farriss</a>, <a href="https://www.drupal.org/u/hestenet">Tim Lehnen</a>, and <a href="https://www.linkedin.com/in/amandinelepape/">Amandine Le Pape</a> for their review.</em></p>
]]></description>
    </item>
    <item>
      <title>Open Source is four kinds of economic goods</title>
      <link>https://dri.es/open-source-is-four-kinds-of-economic-goods</link>
      <guid>https://dri.es/open-source-is-four-kinds-of-economic-goods</guid>
      <pubDate>Tue, 01 Sep 2026 06:50:24 -0400</pubDate>
      <description><![CDATA[<p>An Open Source project contains several economic goods. Its code, infrastructure, services, maintainer time, and commercial ecosystem each behave differently. Treating all of them as a single commons hides who pays today, who controls access, and who needs to pay.</p>
<p>Economists often classify goods along two dimensions. The first is <em>excludability</em>: how easy it is to keep someone from using something. The second is <em>rivalry</em>: whether one person's use leaves less for everyone else.</p>
<p>Combining those dimensions produces <a href="https://doi.org/10.1177/0951692803015003002">four kinds of goods</a>.</p>
<table>
  <colgroup>
  <col style="width: 14%">
  <col style="width: 43%">
  <col style="width: 43%">
</colgroup>
  <thead>
  <tr>
  <th></th>
  <th>Excludable</th>
  <th>Non-excludable</th>
</tr>
</thead>
  <tbody>
  <tr>
  <th>Rival</th>
  <td><strong>Private goods</strong><br>Sandwiches</td>
  <td><strong>Common-pool resources</strong><br>Fish in the ocean</td>
</tr>
  <tr>
  <th>Non-rival</th>
  <td><strong>Toll goods</strong><br>Toll roads, until they reach capacity</td>
  <td><strong>Public goods</strong><br>Weather forecasts</td>
</tr>
</tbody>
</table>
<p>A <em>private good</em> is both excludable and rival. A sandwich is a simple example: the shop can choose whether to sell it to me (excludable), but once I eat it, nobody else can (rival).</p>
<p>A <em>public good</em> is hard to exclude people from and is not used up through use. A weather forecast is a good example: once published, it is hard to prevent people from using it (non-excludable), and one person's use does not make the forecast less available to anyone else (non-rival).</p>
<p>A <em>common-pool resource</em> is also hard to exclude people from, but it is rival. Fish in the ocean are the standard example. It's hard to prevent people from fishing the ocean (non-excludable), but one person's catch leaves fewer fish for everyone else (rival).</p>
<p>A <em>toll good</em> is excludable but largely non-rival until it reaches capacity. A toll road can keep drivers out (excludable), but one more car does not reduce anyone else's access until traffic builds up (non-rival until congestion). James Buchanan also called these <a href="https://doi.org/10.2307/2552442">club goods</a>.</p>
<h2>The four goods in an Open Source project</h2>
<p>People often describe Open Source as both a public good and a commons, but the terms answer different questions. A public good describes a resource's economic characteristics; a commons describes how shared resources are governed.</p>
<p>An ecosystem can be governed as a commons even when its parts have different economic characteristics. That is why it's useful to classify things like code, infrastructure, maintainer time, or collective reputation separately.</p>
<table>
  <colgroup>
  <col style="width: 14%">
  <col style="width: 43%">
  <col style="width: 43%">
</colgroup>
  <thead>
  <tr>
  <th></th>
  <th>Excludable</th>
  <th>Non-excludable</th>
</tr>
</thead>
  <tbody>
  <tr>
  <th>Rival</th>
  <td><strong>Private goods</strong><br>Dedicated support, consulting, and maintainer attention</td>
  <td><strong>Common-pool resources</strong><br>The pool of commercial opportunities the project generates</td>
</tr>
  <tr>
  <th>Non-rival</th>
  <td><strong>Toll goods</strong><br>Access to project-operated package registries, update and security data services, and marketplace visibility</td>
  <td><strong>Public goods</strong><br>Released Open Source code</td>
</tr>
</tbody>
</table>
<p>Open Source code itself behaves much like a <a href="https://doi.org/10.2307/1925895">public good</a>. Making another copy does not reduce what others can use. And because Open Source licenses allow recipients to redistribute the code, restricting access becomes difficult after release.</p>
<p>Steven Weber goes further in <a href="https://www.hup.harvard.edu/books/9780674018587"><em>The Success of Open Source</em></a>, describing Open Source as <em>anti-rival</em> (not a dimension shown in the table). Under the right conditions, its value can grow as adoption and participation grow. More users can bring more testing, bug reports, features, documentation, promotion, and other contributions.</p>
<p>But the code is only one part of an Open Source project.</p>
<p>Many large projects are <a href="https://dri.es/license-only-versus-stewarded-open-source">Stewarded Open Source</a>. A foundation, company, or community operates its package registries and build systems, responds to security issues, coordinates releases, and more. Each of those activities has its own economics and can be mapped to the four types of economic goods.</p>
<p>A project-operated package registry or the data service behind in-product update and security notifications can behave like a toll good. The project could rate-limit access, require accounts, or reserve additional capacity and features for paying customers.</p>
<p>Support and consulting are private goods. A contract determines who receives the service, and an hour spent helping one customer cannot also be spent helping another.</p>
<p>Maintainer attention can also behave like a private good. Access to dedicated review time can be restricted (excludable), and time spent reviewing one patch cannot also be spent on another (rival).</p>
<p>However, that private input can produce a public good. A funder can pay a maintainer to fix a bug or review a patch, but once the improvement is released under the project's Open Source license, anyone can use and redistribute it. Economists call this the <a href="https://doi.org/10.1016/0047-2727%2886%2990024-1">private provision of a public good</a>.</p>
<p>The commercial opportunities an Open Source project generates are a common-pool resource. Customers arrive looking for help because the project has a reputation, and any provider can pursue them (non-excludable). But an engagement won by one provider is gone for the others (rival). The pool of potential customers is the ocean; a signed contract is a fish that has been caught.</p>
<p>What keeps that pool stocked is the project's <a href="https://doi.org/10.18352/ijc.657">collective reputation</a>. It is built by many contributors, and every provider draws on it, whether they contributed or not. A provider can win engagements from the pool without helping maintain the project that produces them. Poor-quality work or unhealthy competition can weaken the reputation, and a weaker reputation means fewer customers arrive for everyone.</p>
<p>This is why I have argued that Open Source communities should help customers distinguish between <a href="https://dri.es/solving-the-maker-taker-problem">Makers and Takers</a>. Making contribution visible steers commercial demand toward providers that help sustain the project and the reputation that keeps customers coming.</p>
<p>A project-operated marketplace that lists service providers is one way to do this. Access to it can behave like a toll good: the project controls which providers it lists (excludable), while listing one more provider usually does not prevent others from participating (non-rival). Open Source gives providers rights to the code, not a right to promotion by the project. A project can therefore require providers to contribute as a condition of marketplace participation.</p>
<h2>A funding rule for stewardship</h2>
<p>For an Open Source project, this framework leads to two practical questions: what can it charge for, and what does it need to budget for?</p>
<p>Excludability shows where an Open Source project can control access and therefore where direct pricing is possible. For example, a project could charge for access to its package registry, security notification service, or provider listings in its marketplace. In each case, the project controls something separate from the released code, which remains available to everyone.</p>
<p>Rivalry shows what use can deplete and therefore where capacity must be budgeted. Copies of code do not run out. Bandwidth, server capacity, staff time, and maintainer attention do. A project may still choose to provide them for free, but <a href="https://dri.es/open-source-is-a-cost-allocation-system">someone always has to absorb their cost</a>.</p>
<p>Together, the two dimensions suggest a simple funding model: <strong>revenue from excludable goods can pay for rival resources</strong>, while Open Source code remains a public good.</p>
<p>That does not mean every excludable service should be put behind a paywall. Charging everyone to use a package registry could slow adoption by making the software harder to try. It's why I previously proposed <a href="https://dri.es/open-source-infrastructure-deserves-a-business-model">a tiered model for Open Source infrastructure</a>: keep core services free for individuals and small projects, while asking organizations that use them at scale to help pay for what they consume.</p>
<p>Across the four categories, projects can look to private and toll goods for funding, protect common-pool resources from depletion, and use that funding to pay for the work that produces public goods.</p>
<p>Projects that do not make those arrangements explicit will continue to depend on invisible subsidies for infrastructure and maintainer work.</p>
]]></description>
    </item>
    <item>
      <title>Open Source is a cost-allocation system</title>
      <link>https://dri.es/open-source-is-a-cost-allocation-system</link>
      <guid>https://dri.es/open-source-is-a-cost-allocation-system</guid>
      <pubDate>Thu, 27 Aug 2026 15:21:42 -0400</pubDate>
      <description><![CDATA[<p>Open Source is usually described as a licensing model, a development model, or a production model. All three descriptions are useful, but they leave something out. Every Open Source ecosystem is also a <em>cost-allocation system</em>.</p>
<p>Keeping software relevant and dependable requires people to write code, review contributions, prepare releases, investigate security reports, operate infrastructure, write documentation, answer questions, and support upgrades and migrations. Someone always bears those costs.</p>
<p>A proprietary vendor can tie access to payment: every license or subscription sold helps fund developers, security work, infrastructure, and releases.</p>
<p>Open Source breaks the link between access and payment. Anyone can redistribute the software at any price, including zero. Payment is therefore not a condition of using or redistributing it, and those rights do not themselves provide a durable mechanism for funding production and maintenance.</p>
<p>But separating payment from access does not make the costs disappear. Costs are distributed across maintainers, employers, foundations, sponsors, and users. A maintainer may volunteer their time. An employer may pay a developer to contribute. A foundation may operate infrastructure.</p>
<p>Because these costs are distributed rather than collected through a single transaction, they are harder to see and harder to fund.</p>
<p>Technical choices often shape where those costs fall. That allocation is not always deliberate; it can emerge slowly from decisions that were individually reasonable and become visible only years later.</p>
<p>Drupal's update service recently gave me a good example. Drupal sites periodically contact <code>updates.drupal.org</code> to ask whether new versions of Drupal or any installed add-ons are available. Drupal calls these add-ons &quot;contributed projects&quot;, such as modules and themes maintained by members of the community.</p>
<p>The current design sends one request for Drupal itself and one for every contributed project installed on the site. A site with 100 contributed projects therefore makes 101 requests each time it checks for updates, by default, once a day.</p>
<p>There were good reasons for that design. Each project's release history could be stored as a static file and served repeatedly without rebuilding it for every request. The design was straightforward, and it worked.</p>
<p>But Drupal grew. Today, <code>updates.drupal.org</code> serves nearly a billion requests a month. The file containing <a href="https://updates.drupal.org/release-history/drupal/current">Drupal Core's release history</a> is roughly half a megabyte by itself. Based on the number of requests for that file, I estimate that Drupal Core update checks alone may account for roughly 50 to 75 TB of data transfer each month. Traffic for contributed projects comes on top of that.</p>
<p>No one chose to make <code>updates.drupal.org</code> transfer tens of terabytes of release data each month. That scale emerged gradually as Drupal grew, from a design that had been reasonable when the ecosystem was smaller.</p>
<p>Part of the challenge is that, in Open Source, the people who benefit, the people who decide, and the people who bear the costs are often different and may have no formal obligations to one another.</p>
<p>Site owners benefit from reliable update notifications, usually without paying the Drupal Association for them. Drupal Association staff operate the update service, while the Association bears its traffic and infrastructure costs. But the code that determines how sites make those requests lives in Drupal Core, where changes require the involvement of Core committers. The Core committers do not report to the Drupal Association, so the Association cannot change that behavior on its own.</p>
<p>In practice, Drupal Association staff and Core committers collaborate closely. That collaboration is important because decision-making authority and cost-bearing sit with different groups.</p>
<p>The people bearing material costs need a way to make those costs visible and influence the decision, even if they do not control it.</p>
<p>This example shows why every Open Source architecture is also a cost-allocation system. Good governance considers that allocation up front, monitors its effects over time, and revisits it when it becomes unsustainable.</p>
<p>Understanding an Open Source system therefore requires more than understanding its code or license. We also need to understand who benefits, who decides, and who bears the costs as the system grows. Once those relationships are visible, a community can decide whether the allocation is sustainable or whether the architecture should change.</p>
]]></description>
    </item>
    <item>
      <title>Finding related posts with embeddings</title>
      <link>https://dri.es/finding-related-posts-with-embeddings</link>
      <guid>https://dri.es/finding-related-posts-with-embeddings</guid>
      <pubDate>Wed, 26 Aug 2026 04:40:02 -0400</pubDate>
      <description><![CDATA[<p>I added a new feature to my blog: a list of related posts at the bottom of each post. I implemented it using embeddings, and this note documents how.</p>
<p>I looked at how other content management systems identify related posts: most use shared tags, backlinks, manual curation, or embeddings. I chose embeddings, which compare the meaning of each post, because they can uncover connections without shared tags, existing links, or manual curation.</p>
<h2>Embeddings turn meaning into numbers</h2>
<p>An embedding model reads text and returns a vector: a long list of numbers. The model I use, <a href="https://huggingface.co/BAAI/bge-base-en-v1.5"><code>bge-base-en-v1.5</code></a> from the Beijing Academy of Artificial Intelligence (BAAI), returns 768 numbers for each post.</p>
<p>For one of my posts, the first handful of those coordinates looks something like this:</p>
<pre><code class="language-text">[ 0.021, -0.045, 0.038, -0.012, 0.007, ..., 0.019 ]
</code></pre>
<p>You can think of those 768 numbers as coordinates in a high-dimensional meaning space, where each dimension captures some pattern the model learned from text.</p>
<p>Conceptually, it is a bit like tagging each blog post with hundreds of auto-generated tags, except that these tags are unnamed (they are just numbers) and distributed (meaning is spread across all of them). Together, the 768 numbers place the post near other posts with similar meaning.</p>
<p>This is what lets two posts match even when they use different words. During training, the model learns that certain words and phrases appear in similar contexts or play similar roles, so it places them near each other in the space. It does not need &quot;car&quot; and &quot;automobile&quot; to share any letters to learn that they are used in related ways.</p>
<h2>Raw cosine similarity makes everything look related</h2>
<p>Once every post has an embedding vector, the next question is how to compare them. This is where I had to dust off a little math. Fortunately, it turned out to be mostly high-school math: averages, angles, and multiplication.</p>
<p>The standard way to compare two vectors is <a href="https://en.wikipedia.org/wiki/Cosine_similarity">cosine similarity</a>. Imagine each vector as an arrow pointing away from the origin. Cosine similarity measures the angle between two of these arrows and then takes the cosine of that angle, which is where the name comes from.</p>
<p>Two arrows pointing in nearly the same direction form a small angle, and the cosine of a small angle is close to 1, meaning the posts are related. As the arrows spread apart, the cosine falls: at a right angle it is 0, and for arrows pointing in opposite directions it drops to -1, so unrelated posts score closer to 0 or even negative.</p>
<p>In practice, these raw cosine values can be misleading, because embedding models rarely spread their vectors evenly in every direction. They tend to pack most vectors into a narrow cone, a property called <a href="https://arxiv.org/abs/1907.12009">anisotropy</a>. It means that the cosine similarities tend to cluster in a narrow band.</p>
<p>On my blog, the raw cosine similarity between two randomly chosen posts is almost always between 0.5 and 0.75, with a median of 0.64. The practical effect is that almost any two posts look somewhat similar even if they are not.</p>
<h2>Mean-centering reveals what makes each post distinct</h2>
<p>Anisotropy has several known fixes. The easiest is <em>mean-centering</em>, which is what I use and what the rest of this section explains.</p>
<p>Other solutions include <a href="https://arxiv.org/abs/1702.01417">All-but-the-top</a>, which removes the average and the next few strongest directions. <a href="https://arxiv.org/abs/2103.15316">Whitening</a> stretches the space so every direction carries equal weight (the name comes from white noise).</p>
<p>With mean-centering you simply compute the average vector across all posts and subtract it from every post's vector. Subtracting the average vector from each post removes what all posts have in common; what remains is what makes each post distinct.</p>
<p>A modern model like <code>bge-base-en-v1.5</code> already suffers less from anisotropy than older or simpler encoders: it is trained with <a href="https://en.wikipedia.org/wiki/Self-supervised_learning#Contrastive_self-supervised_learning">contrastive learning</a>, which pushes unrelated texts apart, and version 1.5 was tuned specifically to spread out its similarity scores. That said, centering still made the scores much more useful on my corpus.</p>
<p>An example might help. Imagine three posts with only two numbers each instead of 768:</p>
<pre><code class="language-text">A = (0.90, 0.10)
B = (0.85, 0.80)
C = (0.80, 0.75)
</code></pre>
<p>At first glance, all three posts might look somewhat similar. In every post the first number is high and close to the others (0.90, 0.85 and 0.80). A number that barely changes from post to post tells you little about how they differ, so that first number is not very useful.</p>
<p>The average (mean) of the three vectors is:</p>
<pre><code class="language-text">mean = (0.85, 0.55)
</code></pre>
<p>Now subtract that average from each post:</p>
<pre><code class="language-text">A = ( 0.05, -0.45)
B = ( 0.00,  0.25)
C = (-0.05,  0.20)
</code></pre>
<p>Now the picture is already clearer. B and C both have a positive second number, so they point in roughly the same direction; A's second number is negative, so it points somewhere else.</p>
<p>Before centering, everything looked similar. After centering, the comparison focuses on what is different from the average.</p>
<h2>Normalization reduces comparison to a dot product</h2>
<p>After centering, each vector has a length as well as a direction. Length says how far a post sits from the average, and direction says in what way it differs.</p>
<p>I want to rank posts by what they are about, not by how unusual they are, so only the direction matters. Hence, we normalize each vector by dividing it by its own length, which scales it to length 1 and moves it onto the unit circle (or, in 768 dimensions, the unit sphere), leaving only its direction.</p>
<p>It also makes the comparison cheaper. Cosine similarity is normally the dot product divided by the product of the two vectors' lengths. If both vectors have length 1, that denominator is 1 × 1 = 1, so the expression reduces to the dot product alone: multiply the two lists number by number, then add the results.</p>
<p>Using the same example, the centered vectors for B and C are:</p>
<pre><code class="language-text">B = ( 0.00, 0.25)
C = (-0.05, 0.20)
</code></pre>
<p>First, normalize each vector to length 1. A vector's length is the square root of the sum of its squared numbers (good old Pythagoras, only with more numbers). B has length √(0.00² + 0.25²) = 0.25, while C has length √((-0.05)² + 0.20²) ≈ 0.206, so dividing each vector by its own length gives:</p>
<pre><code class="language-text">B ≈ ( 0.00, 1.00)
C ≈ (-0.24, 0.97)
</code></pre>
<p>Then take the dot product:</p>
<pre><code class="language-text">(0.00 × -0.24) + (1.00 × 0.97) = 0.97
</code></pre>
<p>That is a strong match: the closer the score is to 1, the more the two posts point in the same direction. B and C are nearly aligned.</p>
<p>A, after normalization, points mostly downward. Next to B:</p>
<pre><code class="language-text">A ≈ ( 0.11, -0.99)
B ≈ ( 0.00,  1.00)
</code></pre>
<p>Multiplying them the same way:</p>
<pre><code class="language-text">(0.11 × 0.00) + (-0.99 × 1.00) = -0.99
</code></pre>
<p>That is not a match at all.</p>
<h2>The PHP code is shorter than the explanation</h2>
<p>The production code does the same arithmetic, just with 768 numbers per post instead of two:</p>
<pre><code class="language-php">public static function center(array $raw): array {
  if ($raw === []) {
    return [];
  }
  $mean = array_fill(0, count(reset($raw)), 0.0);
  foreach ($raw as $vector) {
    foreach ($vector as $i =&gt; $value) {
      $mean[$i] += $value;
    }
  }
  $count = count($raw);
  foreach ($mean as $i =&gt; $sum) {
    $mean[$i] = $sum / $count;
  }
  $centered = [];
  foreach ($raw as $nid =&gt; $vector) {
    $norm = 0.0;
    foreach ($vector as $i =&gt; $value) {
      $vector[$i] = $value - $mean[$i];
      $norm += $vector[$i] * $vector[$i];
    }
    // A vector sitting exactly on the mean centers to zero; fall back to 1.0
    // so the division below never hits a zero norm.
    $norm = sqrt($norm) ?: 1.0;
    foreach ($vector as $i =&gt; $value) {
      $vector[$i] = $value / $norm;
    }
    $centered[$nid] = $vector;
  }
  return $centered;
}

public static function topMatches(array $source, array $pool, int $self): array {
  $scores = [];
  foreach ($pool as $nid =&gt; $vector) {
    if ($nid === $self) {
      continue;
    }
    $similarity = 0.0;
    foreach ($source as $i =&gt; $value) {
      $similarity += $value * $vector[$i];
    }
    $scores[$nid] = $similarity;
  }
  arsort($scores);
  return array_keys(array_slice($scores, 0, 3, TRUE));
}
</code></pre>
<p>While my explanation was long, both PHP methods are relatively short. In <code>center()</code>, each vector has the corpus mean subtracted, then is divided by its own length. In <code>topMatches()</code>, I calculate the cosine similarity between one post and every other post, then keep the three highest.</p>
<p>You might expect a vector database to replace all of this. It would replace some of it: storing a vector and asking for the closest three would remove <code>topMatches()</code>, but it would not remove <code>center()</code>. Centering is optional, but it meaningfully improved my results.</p>
<p>A vector database likely makes centering harder. Today I store raw vectors and subtract the average when I compare them, so a new post does not change anything I have stored. A vector database would search what I stored, so the subtraction would have to happen before storing. I'd have to update all stored vectors for every new post or every edit, which feels more complex. Maybe vector databases have a good answer for that; I have not looked.</p>
<h2>One-time embeddings, occasional ranking</h2>
<p>You might wonder how expensive it is to generate these embeddings and compare all these vectors. It turns out to be fast and cheap.</p>
<p>There are two kinds of work, and they happen at different times. Generating an embedding calls an AI model, but happens only once after a post is created or edited. Ranking uses ordinary PHP arithmetic and happens occasionally, when Drupal rebuilds a page's cached related-post list.</p>
<p>I run the model on Cloudflare Workers AI. To generate an embedding, my server makes an HTTPS call that passes the post's text to Cloudflare, which runs the model and returns the 768-number vector. That round trip takes about 250ms. It happens on the first view after a post is created or edited, and the vector is then cached. The model is deterministic, so the same text always produces the same 768 numbers.</p>
<p>Cloudflare bills Workers AI usage in units it calls Neurons and includes 10,000 free each day. Embedding my full archive of roughly 1,500 posts used roughly 4,000 Neurons, and a new post costs about three. Embedding my blog is basically free.</p>
<p>Calculating the related posts never calls the AI model. It all happens in <a href="https://www.drupal.org/">Drupal</a>, my website's content management system. When Drupal needs to build one of the related posts lists, it loads all the stored vectors, centers them, and scores the current post against all the others: roughly 1,500 dot products, each over 768 numbers. This takes around 250ms on my site. After a list has been built, it is cached.</p>
<p>In other words, my website never loads model weights; it just stores the 768 numbers that come back. The machine-learning compute lives at Cloudflare's edge, and my server stays a plain PHP application. None of this needs a vector database or a machine-learning framework: one HTTP call generates the embedding, a key-value store caches it, and a few dozen lines of arithmetic choose the related posts.</p>
<p>Tags are too blunt, backlinks only capture the links I remembered to make, and manual curation does not scale. All three need me to notice the connection first. Using embeddings might sound a bit scary, but they turned out to be easy to implement, fully automated, and able to surface posts I would never have thought to link.</p>
]]></description>
    </item>
    <item>
      <title>The software business after code scarcity</title>
      <link>https://dri.es/the-software-business-after-code-scarcity</link>
      <guid>https://dri.es/the-software-business-after-code-scarcity</guid>
      <pubDate>Mon, 17 Aug 2026 05:35:44 -0400</pubDate>
      <description><![CDATA[<p>If AI can generate an application from a description, is software still worth anything?</p>
<p>I have lived with a version of that question longer than most.</p>
<p>I released <a href="https://www.drupal.org">Drupal</a> for free more than twenty-five years ago, and later co-founded <a href="https://www.acquia.com">Acquia</a>, which has grown into a large enterprise software company built around Drupal.</p>
<p>Granted, Drupal is free in a different way than AI-generated applications are free, but I'm not sure that changes the basic question of how to build a successful business around either one.</p>
<p>Open Source made code abundant by giving people broad rights to use, modify, and redistribute it. AI is lowering the cost of producing code. One lets you copy the software; the other makes it cheaper to recreate software.</p>
<h2>Free code changes what customers pay for</h2>
<p>Because anyone could use Drupal for free, Acquia could never build a durable business around access to the code. From the start, <a href="https://dri.es/acquia-first-decade-the-founding-story">we had to make money another way</a>.</p>
<p>We built that business around helping enterprises build, run, and manage Drupal applications throughout their lifecycle. That includes hosting, but goes well beyond it: the tools and services needed to develop, deploy, secure, scale, monitor, and improve applications in production.</p>
<p>Proprietary SaaS typically bundles access to the application with the hosting and operations required to run it. With Open Source, organizations can run the software themselves or choose who hosts and operates it.</p>
<p>As AI makes applications cheaper to recreate, the traditional SaaS bundle of software and operations comes under pressure. Customers may become less willing to pay for access to application functionality without becoming any less willing to pay to run and manage applications in production. For Open Source businesses those economics are not new.</p>
<h2>Dependability becomes the product</h2>
<p>Software can be free, or nearly free, without becoming cheap to depend on. The more people and organizations depend on a system, the more of its value comes from operating it securely, reliably, and at scale.</p>
<p class="pullquote">Once people depend on an application, the cost of its failure has little to do with how much it cost to build. An application that costs $1,000 to build can still cause a $10 million failure.</p>
<p>As AI makes enterprise applications easier to create, adapt, and integrate, they still have to be deployed, secured, scaled, monitored, and run reliably over time. As software cost comes down, dependability becomes a differentiator.</p>
<p>Linux is abundant; dependable cloud infrastructure is a service worth paying for. Drupal is abundant; dependable digital experience infrastructure is a service worth paying for.</p>
<p>Acquia has lived with those economics for nearly 20 years. Drupal made the code abundant, so we built our business around helping organizations build, run, and improve what they created with it. As AI makes code cheaper to generate, that business model may start to look a lot less unusual.</p>
<p>Either way, more software companies will have to answer the same question: if code and capabilities are abundant, what are customers really paying you for?</p>
]]></description>
    </item>
    <item>
      <title>Helping agents discover my site search with MCP</title>
      <link>https://dri.es/helping-agents-discover-my-site-search-with-mcp</link>
      <guid>https://dri.es/helping-agents-discover-my-site-search-with-mcp</guid>
      <pubDate>Tue, 04 Aug 2026 11:01:09 -0400</pubDate>
      <description><![CDATA[<p>This is the third post in a series about making my site's search available to AI agents. First, I published an <a href="https://dri.es/helping-agents-discover-my-site-search-with-an-api-catalog">API Catalog</a>, which helps agents that already know to check my site find its search API. Second, I added an <a href="https://dri.es/helping-agents-discover-my-site-search-with-agentic-resource-discovery">Agentic Resource Discovery</a> (ARD) entry so my search can be indexed by &quot;AI registries&quot; (think search engines for AI agents).</p>
<p>So far, no agent has found my search on its own. The API Catalog has been a <a href="https://datatracker.ietf.org/doc/html/rfc9727">published IETF standard</a> for over a year but I found no evidence that either OpenAI or Anthropic checks for it. ARD is newer, still a <a href="https://agenticresourcediscovery.org/spec/">v0.9 draft</a>, and I found no evidence that OpenAI or Anthropic supports it either.</p>
<p>In the meantime, what does work is a custom Agent Skill that points my AI assistants to the API Catalog, which leads them to <a href="https://dri.es/openapi.json">my OpenAPI description</a> and from there to the search itself. If and when agents adopt one or more of these discovery standards, I might be able to drop the skill and have it all work automagically.</p>
<p>Until last week, I had decided <a href="https://modelcontextprotocol.io/">Model Context Protocol</a> (MCP) was not worth implementing for my search API endpoint. It required an initialization handshake and could involve protocol-level sessions. My search doesn't need either: every query is stateless and anonymous. MCP felt like overkill for my simple use case.</p>
<p>The <a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog">2026-07-28 revision</a> changed my mind because both the protocol-level session and the initialization handshake are gone. Every request can now be self-contained, which means that, for a service like mine, an MCP server is basically a POST route that returns JSON.</p>
<p>So I went ahead and implemented MCP support for my search endpoint. The whole thing came to one route and three small RPC methods: fewer than 150 lines of code, excluding tests.</p>
<h2>You can call it from any terminal</h2>
<p>In my testing, adding my site as a custom MCP connector to Claude Desktop, Claude Code, or ChatGPT still fails. These clients don't support the new protocol yet, which is understandable because it is only a week old. Once they catch up, anyone will be able to add dri.es as a connector, not just me.</p>
<p>Until then, an easy way to talk to my site with an MCP client is Simon Willison's <a href="https://github.com/simonw/mcp-explorer">mcp-explorer</a>. You can run it with <code>uvx</code>, which comes with <a href="https://docs.astral.sh/uv/"><code>uv</code></a>:</p>
<pre><code class="language-bash">uvx mcp-explorer list https://dri.es/mcp
uvx mcp-explorer call https://dri.es/mcp find_posts -a query &quot;open source&quot;
</code></pre>
<p>The first command prints each tool's name, description, and arguments. The second runs a search across my posts and returns up to twenty results.</p>
<p>Alternatively, you can use the raw protocol by running this <code>curl</code> command from a terminal:</p>
<pre><code class="language-bash">curl -s https://dri.es/mcp \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;MCP-Protocol-Version: 2026-07-28&quot; \
  -H &quot;Mcp-Method: tools/call&quot; \
  -H &quot;Mcp-Name: find_posts&quot; \
  -d '{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:1,&quot;method&quot;:&quot;tools/call&quot;,&quot;params&quot;:{&quot;name&quot;:&quot;find_posts&quot;,&quot;arguments&quot;:{&quot;query&quot;:&quot;open source&quot;},&quot;_meta&quot;:{&quot;io.modelcontextprotocol/protocolVersion&quot;:&quot;2026-07-28&quot;}}}' \
  | jq .result.structuredContent
</code></pre>
<h2>Discovery and invocation are separate layers</h2>
<p>Having implemented support for three new standards, the useful thing I learned is that they are not alternatives.</p>
<p>ARD handles discovery: it gives registries a standard way to index and search for services like mine. OpenAPI and MCP handle invocation by defining how agents can call and use those services.</p>
<p>So the choice is not ARD or MCP. It is ARD plus OpenAPI, or ARD plus MCP.</p>
<p>For an anonymous, read-only API like mine, I prefer OpenAPI. It was easier to implement and anything that can make an HTTP request can use it.</p>
<p>MCP becomes more attractive when you have services that involve multi-step interactions, authentication, or explicit application state.</p>
<p>Ultimately, agent and crawler adoption will decide. Time will tell, but my money is on ARD and MCP right now.</p>
]]></description>
    </item>
  </channel>
</rss>
