<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Hurley</title>
    <description>The latest articles on DEV Community by David Hurley (@dbhurley).</description>
    <link>https://dev.to/dbhurley</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3847728%2F4c1a7baf-362d-482d-b456-9cce4d70afa0.jpg</url>
      <title>DEV Community: David Hurley</title>
      <link>https://dev.to/dbhurley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dbhurley"/>
    <language>en</language>
    <item>
      <title>The Publisher's Dilemma: Block, Tolerate, or Cooperate</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Mon, 30 Mar 2026 15:09:44 +0000</pubDate>
      <link>https://dev.to/dbhurley/the-publishers-dilemma-block-tolerate-or-cooperate-4640</link>
      <guid>https://dev.to/dbhurley/the-publishers-dilemma-block-tolerate-or-cooperate-4640</guid>
      <description>&lt;p&gt;If you run a website with meaningful traffic, you have already encountered this problem, whether you know it or not. AI agents are crawling your pages. They are consuming your content. And you are getting almost nothing back.&lt;/p&gt;

&lt;p&gt;The numbers are stark. &lt;a href="https://blog.cloudflare.com/crawlers-click-ai-bots-training/" rel="noopener noreferrer"&gt;Cloudflare reported in August 2025&lt;/a&gt; that Anthropic's crawlers fetch approximately 38,000 pages for every single page visit they refer back to publishers. OpenAI's ratio is better but still lopsided. Perplexity's ratio actually got worse over the course of 2025, with crawling volume increasing while referral traffic decreased.&lt;/p&gt;

&lt;p&gt;Meanwhile, Google referrals to news sites have been declining since February 2025, coinciding with the expansion of AI Overviews. The Pew Research Center &lt;a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/" rel="noopener noreferrer"&gt;found&lt;/a&gt; that users are less likely to click through to a source when Google displays an AI summary at the top of search results.&lt;/p&gt;

&lt;p&gt;The traffic pipeline that has sustained the web publishing model for two decades is weakening. And the new consumers that are replacing human visitors are not playing by the same economic rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two options publishers have today
&lt;/h2&gt;

&lt;p&gt;Right now, publishers face a binary choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Block everything.&lt;/strong&gt; Add &lt;code&gt;Disallow: /&lt;/code&gt; for GPTBot, ClaudeBot, and every other AI user agent in your robots.txt. The New York Times, The Atlantic, and hundreds of other publishers have taken this approach. It stops the crawling, eliminates the infrastructure cost, and sends a clear signal about content rights.&lt;/p&gt;

&lt;p&gt;But it also makes you invisible to AI. When someone asks ChatGPT or Claude about a topic you cover deeply, your content will not be in the answer. As AI assistants become a primary interface for information discovery (and the trajectory is unmistakable), publishers who block agents are opting out of an increasingly important distribution channel. It is the equivalent of blocking Googlebot in 2005.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B: Allow everything.&lt;/strong&gt; Do nothing. Let the crawlers in. Serve them the same HTML you serve to browsers, with all the CSS, JavaScript, tracking scripts, ad markup, and layout containers. Hope that being included in AI training data and retrieval results translates to some indirect benefit.&lt;/p&gt;

&lt;p&gt;The cost of this option is concrete and measurable. Every agent request hits your CDN, your origin server, and your rendering pipeline. For sites with server-side rendering, each request triggers a full page build. The agent receives your 2.5MB page, extracts maybe 25% of the content, discards the rest, and sends no referral traffic in return.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "cooperate" means
&lt;/h2&gt;

&lt;p&gt;There is a third option that most publishers have not considered, because until recently it did not exist as a practical choice. Instead of blocking agents or serving them raw HTML, you can serve them a format designed for their consumption model.&lt;/p&gt;

&lt;p&gt;The idea is content negotiation for the agent era. When a browser requests your page, you serve HTML (as you always have). When an AI agent requests your page, you serve a structured semantic representation: smaller, faster to process, and containing only the information the agent actually needs.&lt;/p&gt;

&lt;p&gt;This is not a new concept in web architecture. HTTP content negotiation has existed since the 1990s. Servers already serve different content types based on the &lt;code&gt;Accept&lt;/code&gt; header (JSON for APIs, HTML for browsers, images in different formats based on browser support). Extending this pattern to serve structured representations for AI agents is a natural evolution.&lt;/p&gt;

&lt;p&gt;The structured format we have been developing is called SOM (Semantic Object Model). It is a JSON document that organizes page content into typed regions (navigation, main content, sidebar, footer) containing typed elements (headings, paragraphs, links, buttons, form fields) with explicit declarations of available actions. It preserves what agents need and discards what they do not.&lt;/p&gt;

&lt;p&gt;But the specific format matters less than the principle: publishers can choose what agents receive, rather than letting agents extract whatever they can from HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics of cooperation
&lt;/h2&gt;

&lt;p&gt;I published a &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;detailed cost-benefit analysis&lt;/a&gt; as a research paper, but here is the summary.&lt;/p&gt;

&lt;p&gt;We modeled four publisher strategies across three tiers (small blog at 10K agent requests/month, mid-size news site at 1M/month, and large publisher at 50M/month) and compared the annual infrastructure costs of each.&lt;/p&gt;

&lt;p&gt;The savings come from three sources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bandwidth reduction.&lt;/strong&gt; A structured representation is 5-15KB compared to 30-100KB+ for a full HTML page with its associated resources. When you are serving millions of agent requests per month, the bandwidth difference is substantial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compute reduction.&lt;/strong&gt; This is the big one for dynamic sites. Server-side rendered pages require a full render cycle per request: template compilation, database queries, component rendering, HTML serialization. A cached SOM representation is a static JSON file served from CDN with zero origin compute. For sites where SSR costs dominate (which is most modern web applications), this is where the majority of savings come from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bot management simplification.&lt;/strong&gt; Publishers currently spend significant effort on WAF rules, rate limiting, and bot detection to manage agent traffic. Cooperative serving through a dedicated endpoint reduces the adversarial dynamic. You are explicitly offering content for agents through a channel you control, rather than playing defense against crawlers hitting your production infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond cost: what cooperation gets you
&lt;/h2&gt;

&lt;p&gt;Cost savings are the easiest benefit to quantify, but they are not the most important one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content control
&lt;/h3&gt;

&lt;p&gt;When you serve raw HTML to agents, you have no control over what they extract. They might grab a sidebar advertisement and present it as your editorial content. They might pull a number from a related-articles widget and attribute it to your reporting. The Air Canada chatbot &lt;a href="https://dbhurley.com/blog/when-ai-reads-the-web-wrong" rel="noopener noreferrer"&gt;got a bereavement policy wrong&lt;/a&gt; because it could not distinguish policy text from surrounding content.&lt;/p&gt;

&lt;p&gt;When you serve a structured representation, you define exactly what the agent receives. The main article is explicitly labeled as main content. The sidebar is labeled as complementary. Advertisements and tracking are excluded entirely. You are curating the agent's view of your page, the same way you curate the search engine's view through structured data and the API consumer's view through endpoint design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attribution
&lt;/h3&gt;

&lt;p&gt;This is the piece that excites me most about the cooperative model. A structured representation can include provenance metadata: unique identifiers for each content element that enable agents to cite specific sources. Not just "according to nytimes.com," but "according to paragraph 3 of the main content region on nytimes.com/article/xyz, published March 15, 2026."&lt;/p&gt;

&lt;p&gt;This creates the foundation for an attribution system that does not exist today. When agents cite structured sources with element-level provenance, publishers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track which content elements are most frequently cited by agents&lt;/li&gt;
&lt;li&gt;Measure their influence in the agent-mediated information ecosystem&lt;/li&gt;
&lt;li&gt;Build a case for licensing based on demonstrated usage data&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Future-proofing
&lt;/h3&gt;

&lt;p&gt;Agent traffic is growing at 18-30% year over year. Some individual crawlers are growing 300%+ annually. The publishers who build cooperative infrastructure now will have it in place when agent-mediated content discovery becomes the primary channel. The publishers who wait will scramble to catch up, the same way many scrambled to adopt SEO, social sharing, and mobile-responsive design after those transitions were already well underway.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it actually works
&lt;/h2&gt;

&lt;p&gt;There are three levels of implementation, from simple to comprehensive:&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 1: Static SOM files
&lt;/h3&gt;

&lt;p&gt;Generate SOM representations at build time and serve them as static files alongside your HTML. This works for any site with a build pipeline (Hugo, Next.js, Astro, Jekyll, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the SOM compiler&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;plasmate-wasm

&lt;span class="c"&gt;# In your build script, after generating HTML:&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in &lt;/span&gt;public/&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.html&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;plasmate compile &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;%.html&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.som.json"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents discover SOM files through a &lt;code&gt;.well-known/som.json&lt;/code&gt; manifest or &lt;code&gt;&amp;lt;link rel="alternate"&amp;gt;&lt;/code&gt; tags in your HTML. No server-side logic required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 2: Content negotiation
&lt;/h3&gt;

&lt;p&gt;Add a middleware that checks the &lt;code&gt;Accept&lt;/code&gt; header or user agent and serves SOM to agents, HTML to browsers. This is a few lines in any web framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express/Next.js middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accepts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/som+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;isAgentUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;somPathFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// serve HTML as normal&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Level 3: Robots.txt directives
&lt;/h3&gt;

&lt;p&gt;Declare your SOM endpoint in robots.txt so agents know to request it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: *
&lt;span class="n"&gt;SOM&lt;/span&gt;-&lt;span class="n"&gt;Endpoint&lt;/span&gt;: /.&lt;span class="n"&gt;well&lt;/span&gt;-&lt;span class="n"&gt;known&lt;/span&gt;/&lt;span class="n"&gt;som&lt;/span&gt;/{&lt;span class="n"&gt;path&lt;/span&gt;}.&lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;SOM&lt;/span&gt;-&lt;span class="n"&gt;Version&lt;/span&gt;: &lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the cooperative content negotiation model described in our &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;robots.txt extension proposal&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The licensing question
&lt;/h2&gt;

&lt;p&gt;I would be dishonest if I did not address the elephant in the room. Some publishers are pursuing licensing deals with AI companies (OpenAI has signed agreements with several major publishers, and Perplexity has launched a revenue-sharing program). If you can get paid directly for your content, that changes the calculus.&lt;/p&gt;

&lt;p&gt;But licensing deals are available to a handful of the largest publishers. The vast majority of websites, the millions of blogs, documentation sites, small news outlets, government portals, community forums, and niche publications that collectively make up the long tail of the web, will never get a licensing call from OpenAI.&lt;/p&gt;

&lt;p&gt;For those publishers, the choice is not "license or cooperate." It is "serve raw HTML to agents that extract your content without attribution or compensation" versus "serve structured content that costs you less, gives you more control, and positions you for attribution when the infrastructure matures."&lt;/p&gt;

&lt;p&gt;Cooperation is not a substitute for licensing. It is the practical path for publishers who will never get a licensing deal but still want to reduce costs, maintain control, and participate in the agent-mediated web.&lt;/p&gt;

&lt;h2&gt;
  
  
  The precedent
&lt;/h2&gt;

&lt;p&gt;Every major transition in how the web is consumed has followed the same pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new consumer class emerges (search engines, applications, agents)&lt;/li&gt;
&lt;li&gt;Publishers initially resist or ignore the new consumer&lt;/li&gt;
&lt;li&gt;An economic incentive emerges (search visibility, API integrations, agent inclusion)&lt;/li&gt;
&lt;li&gt;Publishers adopt purpose-built infrastructure (sitemaps, APIs, ???)&lt;/li&gt;
&lt;li&gt;Early adopters gain structural advantages that late adopters struggle to overcome&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are between steps 2 and 3 right now. Most publishers are either resisting (blocking agents) or ignoring (serving raw HTML). The economic incentive is forming: agent traffic is growing, agent-mediated discovery is replacing some search traffic, and the cost of serving raw HTML to agents is quantifiable.&lt;/p&gt;

&lt;p&gt;The infrastructure for step 4 is being built. SOM, AWP, the robots.txt extensions, the content negotiation patterns. Whether these specific technologies become the standards or something else does, the directional trend is clear: publishers will eventually serve structured content to agents, because the economics and the incentives demand it.&lt;/p&gt;

&lt;p&gt;The publishers who engage now, while the infrastructure is still forming, get to shape those terms. The publishers who wait will accept whatever standards emerge without their input.&lt;/p&gt;

&lt;p&gt;I know which position I would rather be in. And I suspect most publishers, once they see the numbers, will agree.&lt;/p&gt;




&lt;p&gt;The full cost-benefit analysis is published as a &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;research paper&lt;/a&gt; with worked examples for three publisher tiers, sensitivity analysis across traffic growth scenarios, and case studies for news, e-commerce, and documentation sites.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;David Hurley is the founder of &lt;a href="https://plasmatelabs.com" rel="noopener noreferrer"&gt;Plasmate Labs&lt;/a&gt;. Previously, he founded Mautic, the world's first open source marketing automation platform. He writes at &lt;a href="https://dbhurley.com/blog" rel="noopener noreferrer"&gt;dbhurley.com/blog&lt;/a&gt; and publishes research at &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;dbhurley.com/papers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>contentcreators</category>
    </item>
    <item>
      <title>When AI Reads the Web Wrong</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Mon, 30 Mar 2026 14:27:37 +0000</pubDate>
      <link>https://dev.to/dbhurley/when-ai-reads-the-web-wrong-23c5</link>
      <guid>https://dev.to/dbhurley/when-ai-reads-the-web-wrong-23c5</guid>
      <description>&lt;p&gt;In February 2024, the Civil Resolution Tribunal of British Columbia ordered Air Canada to pay a passenger named Jake Moffatt a partial refund. The reason: Air Canada's AI chatbot had told Moffatt he could book a full-fare flight and apply for a bereavement discount afterward. That was wrong. The airline's actual policy required the discount to be requested before booking. The chatbot read the airline's own website and got the policy backward.&lt;/p&gt;

&lt;p&gt;Air Canada tried to argue that the chatbot was "a separate legal entity that is responsible for its own actions." The tribunal was not persuaded.&lt;/p&gt;

&lt;p&gt;This story gets cited as a cautionary tale about AI hallucination. But I think it illustrates something more specific and more fixable. The chatbot did not hallucinate out of thin air. It read a real web page containing real policy information and then misinterpreted what it found. The page almost certainly had the correct policy buried somewhere in the HTML, surrounded by navigation menus, footer links, promotional banners, cookie consent dialogs, and whatever else airlines pack into their web pages these days.&lt;/p&gt;

&lt;p&gt;The chatbot's error was not a failure of intelligence. It was a failure of comprehension, caused by the format of the input.&lt;/p&gt;


&lt;p&gt;The chatbot did not hallucinate out of thin air. It read a real page with real policy information and misinterpreted what it found. The format of the input caused the error, not the quality of the model.&lt;/p&gt;
&lt;br&gt;

&lt;h2&gt;
  
  
  The format problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;When an AI agent "reads" a web page, it does not see what you see. You see a clean layout with headings, paragraphs, buttons, and images, all arranged in a visual hierarchy that your brain processes in milliseconds. The AI agent sees something very different: tens of thousands of tokens of raw HTML markup, most of which has nothing to do with the actual content of the page.&lt;/p&gt;

&lt;p&gt;I have been measuring this for the past several months as part of building Plasmate. Across 50 real websites, the average web page contains about 33,000 tokens of HTML. Of those, roughly 25% is actual content: the text, the headings, the links, the things you would want an AI to understand. The remaining 75% is presentation markup: CSS class names, inline styles, JavaScript, tracking pixels, layout containers, ad slots, data attributes, SVG icons, and structural dividers that exist only to make the page look right in a browser.&lt;/p&gt;

&lt;p&gt;The AI agent processes all of it. It pays for all of it (literally, token by token). And it has to figure out, on its own, which parts matter and which parts are noise.&lt;/p&gt;

&lt;p&gt;Imagine handing someone a 400-page book and telling them that 300 of those pages are blank or contain random numbers, but the 100 pages with actual content are scattered throughout and not marked in any way. Then ask them to summarize the book accurately. They might get it right most of the time. But sometimes they will latch onto a random number from a blank page and present it as a fact. That is more or less what we are doing to AI agents every time they browse the web.&lt;/p&gt;



&lt;h2&gt;
  
  
  Four ways agents get web pages wrong
&lt;/h2&gt;

&lt;p&gt;After spending months studying how AI agents interact with web content, I have started to see patterns in their errors. Not all mistakes are the same. They fall into distinct categories, and the category tells you something about what caused the mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural errors.&lt;/strong&gt; The agent invents page elements that do not exist. "Click the Subscribe button in the sidebar" when there is no such button. This happens most often when the agent is working from a text-only version of the page (like markdown) that strips out all structural information. The agent knows buttons probably exist on most pages, so it guesses. Sometimes it guesses wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content errors.&lt;/strong&gt; The agent reports facts that are not on the page, or reports the wrong version of a fact that is. A price of $49.99 when the page shows $59.99. This is the classic hallucination, and it can be triggered by noisy input. When the agent is processing 33,000 tokens and trying to find a single number, the probability of grabbing the wrong one goes up with the amount of noise in the input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attribution errors.&lt;/strong&gt; The agent finds the right information but attributes it to the wrong part of the page. It reports a statistic from the sidebar as if it were from the main article. It confuses a promotional offer with the actual product price. This happens because the agent cannot distinguish between page regions. Everything arrives as one flat stream of text, and the agent has to guess where the main content ends and the sidebar begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inference errors.&lt;/strong&gt; The agent draws conclusions that the page does not support. A product is described as "popular" and the agent reports it as "the best-selling item." This type of error is less about the input format and more about the model's tendency to fill in gaps. But noisier, more confusing input makes inference errors more likely, because the agent has less confidence in what it actually found and more temptation to extrapolate.&lt;/p&gt;


&lt;p&gt;Which of these error types caused the Air Canada chatbot to get the bereavement policy wrong? Almost certainly a combination of content and attribution errors. The correct policy was on the page, buried in noise.&lt;/p&gt;
&lt;br&gt;


&lt;p&gt;The Air Canada case was probably a combination of content and attribution errors. The correct bereavement policy was on the page, but surrounded by enough other content that the chatbot either found the wrong paragraph or misattributed a condition from one section to another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why markdown is not the answer
&lt;/h2&gt;

&lt;p&gt;The most common response to the "HTML is too noisy" problem is to convert web pages to markdown before feeding them to an AI agent. Strip out the HTML tags, keep the text. This is what most AI agent frameworks do by default: LangChain, LlamaIndex, CrewAI, and others all convert web pages to plain text or markdown before processing.&lt;/p&gt;

&lt;p&gt;Markdown does solve the noise problem. A page that was 33,000 tokens of HTML becomes about 4,500 tokens of markdown. That is a huge improvement in efficiency. But it creates a new problem: markdown throws away all the structural information along with the noise.&lt;/p&gt;

&lt;p&gt;A markdown representation of a web page cannot tell you which text is a button and which is a heading. It cannot tell you which elements are interactive and which are static. It cannot distinguish between the main content area and the sidebar. It cannot tell you what form fields are available or what options are in a dropdown menu.&lt;/p&gt;

&lt;p&gt;For simple reading tasks, this does not matter. If you just need to extract the text of an article, markdown is great. But for anything that requires understanding the structure of the page, knowing what you can click, figuring out how to fill a form, or navigating a multi-step workflow, markdown is blind.&lt;/p&gt;

&lt;p&gt;This creates an awkward situation for AI agent developers. They use one format (markdown) when they need to read pages, and a completely different format (raw HTML with DOM selectors) when they need to interact with pages. Two systems, two sets of failure modes, no unified understanding of the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the page told the agent what it needed to know?
&lt;/h2&gt;

&lt;p&gt;This is the question I have been working on. Not "how do we make AI smarter at reading HTML," but "how do we give AI a format that is actually designed for it?"&lt;/p&gt;

&lt;p&gt;The idea behind the Semantic Object Model (SOM) is straightforward: take a web page and compile it into a structured representation that preserves what an agent needs (the content, the element types, the interactive affordances, the page regions) while discarding what it does not (the CSS, the scripts, the tracking, the layout containers, the visual presentation).&lt;/p&gt;

&lt;p&gt;The output is a JSON document that organizes the page into typed regions (navigation, main content, sidebar, footer) containing typed elements (headings, paragraphs, links, buttons, form fields) with explicit declarations of what actions are available (click, type, select, toggle). An agent reading a SOM document knows exactly what is on the page, where it is, what type of element it is, and what it can do with it.&lt;/p&gt;

&lt;p&gt;This is not a summary or an extraction. It is a compiled representation of the full page, preserving all the semantic information while eliminating the visual presentation layer. Think of it as the difference between giving someone a blueprint of a building versus giving them a photograph. The photograph is richer in visual detail, but the blueprint tells you what every room is for, where the doors are, and which ones are locked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;We have been running benchmarks across 50 real websites with two different AI models (GPT-4o and Claude Sonnet 4). The efficiency numbers are dramatic:&lt;/p&gt;

&lt;p&gt;SOM uses about 8,300 tokens per page compared to 33,000 for raw HTML. That is a 4x reduction. For navigation-heavy pages, the ratio reaches 5.4x. For pages with heavy advertising, 6x.&lt;/p&gt;

&lt;p&gt;But efficiency is not the point of this post. The point is correctness.&lt;/p&gt;

&lt;p&gt;In our latest research, we set up 150 tasks across six categories: extracting specific facts, comparing information across page sections, identifying navigation structure, summarizing content, handling noisy pages with lots of ads, and identifying interactive elements. We tested each task with all three formats (HTML, markdown, SOM) across four different AI models.&lt;/p&gt;

&lt;p&gt;The results are still being finalized, but the patterns are clear. For tasks that require understanding page structure (navigation, interactive elements, adversarial pages with lots of noise), structured representations produce measurably better results. The agent makes fewer structural errors because it does not have to guess about page structure. It makes fewer attribution errors because regions are explicitly labeled. It makes fewer content errors on noisy pages because the noise has been filtered out at compile time rather than at inference time.&lt;/p&gt;

&lt;p&gt;Perhaps the most interesting finding is about speed. On Claude, SOM is faster than markdown despite using nearly twice as many tokens.&lt;/p&gt;


&lt;p&gt;Claude Sonnet 4 with HTML input: &lt;strong&gt;16.2 seconds&lt;/strong&gt; per task. With SOM input: &lt;strong&gt;8.5 seconds&lt;/strong&gt;. Nearly 2x faster, despite SOM having more tokens than markdown. Structured input reduces the work the model has to do.&lt;/p&gt;
&lt;br&gt;


&lt;p&gt;Our interpretation is that structured input reduces the amount of work the model has to do to understand the page. When the structure is explicit, the model spends less time reasoning about "what is this element?" and "where does the sidebar end?" and more time reasoning about the actual question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me
&lt;/h2&gt;

&lt;p&gt;One thing we built into SOM that I initially considered a nice-to-have turned out to be one of the most important features: provenance tracking. Every element in a SOM document has a stable identifier. When an agent extracts a fact from a SOM page, the system can record which specific element that fact came from.&lt;/p&gt;

&lt;p&gt;This means you can programmatically verify an agent's claims. If the agent says "the product costs $49.99," you can check whether element &lt;code&gt;e_a3f2b1&lt;/code&gt; in the &lt;code&gt;main&lt;/code&gt; region actually contains that price. If it does, the claim is verified. If it does not, you know the agent made an error, and you know it before the claim reaches a user.&lt;/p&gt;

&lt;p&gt;Compare this to verifying a claim against raw HTML or markdown. The best you can do is search for the string "$49.99" somewhere in the document. If it appears in an ad or in a "customers also bought" section rather than the product listing, you cannot tell the difference. The claim looks verified even though the agent found the number in the wrong place.&lt;br&gt;
For high-stakes applications (medical information, financial advice, legal policy lookup), this is the difference between an AI assistant you can trust and one you cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to Air Canada
&lt;/h2&gt;

&lt;p&gt;Would structured representations have prevented the Air Canada chatbot error? I think they would have helped significantly. The bereavement policy was on the page. The issue was that the chatbot could not reliably distinguish the policy text from the surrounding content, or it confused conditions from adjacent sections.&lt;/p&gt;

&lt;p&gt;A SOM representation of that page would have placed the bereavement policy in a clearly labeled content region, with each policy condition as a distinct paragraph element. The chatbot would have received a clean, structured view of the policy rather than a wall of HTML that happened to contain the policy somewhere within it.&lt;/p&gt;

&lt;p&gt;Would that guarantee a correct answer? No. AI agents can still make inference errors regardless of input format. But it would eliminate the structural and attribution errors that are caused by noisy, ambiguous input. And for a chatbot handling customer-facing policy questions, eliminating those error categories is worth a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means going forward
&lt;/h2&gt;

&lt;p&gt;The web was built for human eyes. Every page on the internet is designed to be rendered as pixels on a screen. That made perfect sense when humans were the only consumers of web content.&lt;/p&gt;

&lt;p&gt;They are not anymore. AI agents now account for a growing share of web traffic. Cloudflare reported that crawler traffic grew 18% in a single year, with some AI crawlers growing over 300%. These agents are browsing billions of pages, and every one of those pages is served in a format designed for someone who will never use it.&lt;/p&gt;

&lt;p&gt;The fix is not to make agents better at reading HTML. It is to give agents a format designed for them, the same way we gave search engines sitemaps and gave applications APIs. Each new class of web consumer has eventually gotten infrastructure designed for its consumption model. AI agents are the next consumer class, and they need the same.&lt;/p&gt;

&lt;p&gt;I published a &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;detailed research paper&lt;/a&gt; on information fidelity that goes deep on the methodology, the hallucination taxonomy, and the experimental framework. If you are building agent systems or evaluating web content representations, the paper has the technical depth. This post is the accessible version: AI agents read the web wrong because we are feeding them the wrong format, and there is a better way.&lt;br&gt;
&lt;em&gt;David Hurley is the founder of &lt;a href="https://plasmatelabs.com" rel="noopener noreferrer"&gt;Plasmate Labs&lt;/a&gt;. Previously, he founded Mautic, the world's first open source marketing automation platform. He writes at &lt;a href="https://dbhurley.com/blog" rel="noopener noreferrer"&gt;dbhurley.com/blog&lt;/a&gt; and publishes research at &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;dbhurley.com/papers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>machinelearning</category>
      <category>programming</category>
    </item>
    <item>
      <title>The Billion Dollar Tax on AI Agents</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Mon, 30 Mar 2026 14:21:43 +0000</pubDate>
      <link>https://dev.to/dbhurley/the-billion-dollar-tax-on-ai-agents-15pm</link>
      <guid>https://dev.to/dbhurley/the-billion-dollar-tax-on-ai-agents-15pm</guid>
      <description>&lt;p&gt;I want to walk you through a number that I keep coming back to, one that I think deserves more attention than it gets. The number is somewhere between one billion and five billion dollars per year. That is our estimate of how much the AI industry spends, collectively, processing web page markup that no agent will ever use.&lt;/p&gt;

&lt;p&gt;Not because the agents are inefficient. Not because the models are wasteful. Because the web serves content in a format designed for human eyes, and agents are paying the full cost of that visual presentation layer every time they read a page.&lt;/p&gt;

&lt;p&gt;Let me show you where this number comes from.&lt;/p&gt;



&lt;h2&gt;
  
  
  Start with a single web page
&lt;/h2&gt;

&lt;p&gt;Pick any web page. Go to your favorite news site, an e-commerce product page, a documentation site, a government portal. Right-click, view source. What you see is HTML: a mix of content (the text, the links, the headings) and presentation (CSS classes, inline styles, layout containers, tracking scripts, ad markup, SVG icons, data attributes).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://almanac.httparchive.org/en/2025/page-weight" rel="noopener noreferrer"&gt;HTTP Archive's 2025 Web Almanac&lt;/a&gt; reports that the median home page now weighs 2.86 MB on desktop and 2.56 MB on mobile. But what matters for AI agents is not the total page weight (which includes images, fonts, and videos that agents do not load). What matters is the HTML document itself and the JavaScript that populates it.&lt;/p&gt;

&lt;p&gt;Across our 50-site benchmark, the average rendered web page contains about 33,000 tokens when fed to a language model tokenizer. That is the number the agent actually processes: 33,000 tokens of HTML markup shoved into the model's context window.&lt;/p&gt;

&lt;p&gt;How much of that is actual content? We measured this by comparing raw HTML to SOM (a structured representation that preserves semantic content while stripping presentation). The SOM version of the same pages averages about 8,300 tokens.&lt;/p&gt;

&lt;p&gt;That means roughly 24,900 tokens per page, about 75%, encode nothing that the agent needs. CSS class names like &lt;code&gt;flex items-center justify-between px-4 py-2 bg-white dark:bg-gray-900&lt;/code&gt;. Tracking scripts. Layout dividers. Ad containers. Cookie consent dialogs. SVG path data for icons. The visual scaffolding that makes a page look right in a browser but contributes nothing to an agent's understanding of what the page says or what you can do on it.&lt;br&gt;
The agent processes all of it. Every token. And somebody pays for every token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scale it up
&lt;/h2&gt;

&lt;p&gt;Now take that per-page waste and multiply it by the number of pages AI agents browse every day. This is where the math gets interesting and, I will be honest, where it requires some estimation. The exact number of daily agent page fetches is not publicly disclosed by any major AI company. But we can build a reasonable model from what is public.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.cloudflare.com/radar-2025-year-in-review/" rel="noopener noreferrer"&gt;Cloudflare's 2025 Year in Review&lt;/a&gt; reports that approximately 30% of all web traffic is bot traffic. AI-specific crawlers (GPTBot, ClaudeBot, Meta-ExternalAgent, Amazonbot, and others) account for about 4.2% of all HTML request traffic, separate from Googlebot's 4.5%. And this is growing fast: from May 2024 to May 2025, &lt;a href="https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/" rel="noopener noreferrer"&gt;AI crawler traffic grew 18% overall&lt;/a&gt;, with GPTBot specifically growing 305% in that period.&lt;/p&gt;

&lt;p&gt;But raw crawler traffic (which is mostly training data collection) is different from what agents do when they browse on behalf of users. We need to separate the two.&lt;/p&gt;

&lt;p&gt;When you ask ChatGPT to look something up and it browses the web, that is a user-action page fetch. When GPTBot crawls Wikipedia at 3 AM to build a training dataset, that is training crawl traffic. The training traffic is much larger in volume, but the user-action traffic is what incurs per-request LLM inference costs, because the fetched page goes directly into a model's context window.&lt;/p&gt;

&lt;p&gt;Using public user count data (OpenAI has reported over 300 million weekly active users for ChatGPT, Perplexity has disclosed 20 million monthly users, Anthropic has not disclosed but is estimated at several million Claude users), we built a bottom-up model of daily page fetches. The estimate: roughly 400 million user-action page fetches per day, across all major AI agents combined.&lt;/p&gt;

&lt;p&gt;400 million pages. 24,900 wasted tokens each. At a weighted average API price of $0.75 per million input tokens (blending GPT-4o at $2.50, GPT-4o Mini at $0.15, Claude Sonnet at $3.00, Gemini at $1.25).&lt;/p&gt;

&lt;p&gt;The math: 400M pages/day x 24,900 waste tokens x $0.75/M tokens x 365 days = approximately $2.7 billion per year.&lt;/p&gt;

&lt;p&gt;A separate top-down model calibrated against Cloudflare's total traffic volume produces a higher estimate. Combining the two approaches, we bracket the annual industry-wide token waste at $1 billion to $5 billion per year.&lt;/p&gt;


&lt;p&gt;400M pages/day × 24,900 waste tokens × $0.75/M tokens × 365 days = &lt;strong&gt;$2.7 billion/year&lt;/strong&gt;. And this only counts user-triggered browsing, not autonomous agent workloads.&lt;/p&gt;
&lt;br&gt;

&lt;h2&gt;
  
  
  That is a lot of money. Is it real?
&lt;/h2&gt;

&lt;p&gt;It is worth being explicit about the uncertainties here. The exact number depends heavily on three variables: how many pages agents fetch per day (our biggest source of uncertainty), the effective LLM price per token (which is falling and varies by model), and how much preprocessing agents already do (some strip HTML to markdown, some truncate, some cache).&lt;/p&gt;

&lt;p&gt;We account for all of these. Our model assumes that 45% of agents already convert to markdown (which reduces waste by about 70%), 30% truncate HTML (reducing waste by about 30%), and 15% of fetches are cache hits. After these adjustments, the effective waste per page drops from 24,900 tokens to about 13,300 tokens. The billion-dollar figure already includes these reductions.&lt;/p&gt;

&lt;p&gt;You could argue the number is lower if agent usage grows slower than projected, or if LLM prices continue to drop. Both are plausible. But agent usage is growing much faster than prices are dropping. GPTBot's traffic grew 305% in a single year. LLM input prices have not fallen 305% in the same period. The total cost is going up, not down.&lt;/p&gt;

&lt;p&gt;You could also argue the number is higher, because our model only counts user-action fetches (where someone explicitly asks an agent to browse). It does not count autonomous agent workloads: monitoring services, price comparison engines, research pipelines, and other machine-to-machine browsing that runs continuously without human prompting. Those workloads are growing rapidly and process far more pages per instance than a human user would.&lt;/p&gt;

&lt;p&gt;The honest answer: we believe $1B to $5B is a reasonable bracket. The central estimate of $2.7B probably understates autonomous workloads and overstates the effectiveness of current preprocessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the money actually go?
&lt;/h2&gt;

&lt;p&gt;This is the part that I find most frustrating. The wasted tokens do not simply vanish. They consume real resources at every stage of the inference pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPU compute.&lt;/strong&gt; Every input token passes through the model's attention layers. The self-attention mechanism has quadratic complexity with respect to input length: doubling the input more than doubles the compute. When 75% of the input is presentation noise, the model spends the majority of its attention budget on tokens that carry no useful information. This is not just a billing abstraction. It is actual electricity consumed by actual GPUs running actual matrix multiplications on CSS class names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context window displacement.&lt;/strong&gt; Language models have finite context windows. A 128K-token window sounds generous until you realize that a single HTML page consumes 33K of it. An agent that needs to analyze five pages in a single pass can only fit one or two in HTML, but could fit all five in a structured format. The wasted tokens directly limit what the agent can reason about in a single inference call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency.&lt;/strong&gt; More input tokens mean longer time-to-first-token. In our &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;WebTaskBench evaluation&lt;/a&gt;, we measured the latency impact directly. On Claude Sonnet 4, the average task took 16.2 seconds with raw HTML input versus 8.5 seconds with SOM input. The agent was nearly twice as fast, simply because it had less noise to process. GPT-4o showed a similar pattern: 2.74 seconds with HTML versus 1.44 seconds with SOM.&lt;/p&gt;

&lt;p&gt;That latency difference is not just about user experience (though users do notice when their AI assistant takes 16 seconds instead of 8). It is about throughput. A serving cluster that can handle N requests per second with HTML input can handle roughly 2N requests per second with structured input. The infrastructure savings compound on top of the token cost savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What nobody talks about: the crawl-to-click gap
&lt;/h2&gt;

&lt;p&gt;There is a dimension to this problem that goes beyond token costs, and I think it is the more important one for the long-term health of the web.&lt;/p&gt;

&lt;p&gt;Cloudflare published a remarkable dataset in August 2025 titled &lt;a href="https://blog.cloudflare.com/crawlers-click-ai-bots-training/" rel="noopener noreferrer"&gt;"The crawl-to-click gap."&lt;/a&gt; The core finding: AI crawlers consume vastly more content than they send back as referral traffic. The ratios are staggering.&lt;/p&gt;

&lt;p&gt;In July 2025, Anthropic's crawlers fetched approximately 38,000 pages for every single page visit they referred back to a publisher. That is a 38,000:1 crawl-to-refer ratio. Earlier in the year it was 286,000:1. Perplexity's ratio actually got worse over 2025, with more crawling but fewer referrals, reaching 194:1 by July.&lt;/p&gt;

&lt;p&gt;Compare this to Google. For all the complaints about Google hoarding traffic (and &lt;a href="https://blog.cloudflare.com/crawlers-click-ai-bots-training/" rel="noopener noreferrer"&gt;the data does show&lt;/a&gt; Google referrals to news sites declining since February 2025, coinciding with the expansion of AI Overviews), Google's crawl-to-refer ratio is in the single digits. It crawls pages and sends users back.&lt;/p&gt;

&lt;p&gt;AI companies crawl pages and keep the value.&lt;br&gt;
This is the economic context that makes the token waste problem more than an efficiency issue. Publishers are paying to serve content to agents that extract the value and return nothing. The infrastructure costs of serving those requests (bandwidth, compute, CDN, origin rendering) come out of the publisher's budget. And the content served in those requests is 75% visual presentation that the agent throws away.&lt;/p&gt;

&lt;p&gt;The publisher pays to generate it. The agent pays to process it. And neither party gets value from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the ten biggest agent frameworks actually do
&lt;/h2&gt;

&lt;p&gt;Part of our research involved surveying the default web content handling in 10 major agent frameworks. The results were, frankly, depressing.&lt;/p&gt;

&lt;p&gt;LangChain, LlamaIndex, and CrewAI, the three most popular agent orchestration frameworks, all default to BeautifulSoup's &lt;code&gt;get_text()&lt;/code&gt; method. This is the most aggressive possible extraction: it strips every HTML tag and returns flat, unstructured text. The result is small (good for tokens) but has lost all structural information: element types, interactive affordances, page regions, everything that distinguishes a button from a heading from a link.&lt;/p&gt;

&lt;p&gt;Dedicated scraping tools like Crawl4AI, Firecrawl, and Jina Reader use markdown extraction, which is more sophisticated. Markdown preserves headings, links, and basic formatting. But it still discards element types (a button looks the same as a link), interactive affordances (you cannot tell what you can click), and page regions (main content is indistinguishable from sidebar content).&lt;/p&gt;

&lt;p&gt;Browser Use and Stagehand use accessibility tree extraction, which is the closest to a structured representation. But accessibility trees are designed for screen readers, not AI agents. They include every element on the page (including the 200 decorative ARIA landmarks in a typical site footer) and produce output that is often as verbose as the original HTML.&lt;/p&gt;

&lt;p&gt;None of the ten frameworks we surveyed use a structured semantic representation by default. Zero out of ten.&lt;/p&gt;


&lt;p&gt;The entire ecosystem is either stripping web pages down to bare text (losing structure) or passing through raw HTML (paying for noise). There is no middle ground in production use today.&lt;/p&gt;
&lt;br&gt;



&lt;p&gt;&lt;strong&gt;LangChain, LlamaIndex, CrewAI:&lt;/strong&gt; BeautifulSoup get_text(). Strips all HTML, returns flat text. Minimal tokens, zero structure.&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&lt;strong&gt;Crawl4AI:&lt;/strong&gt; Custom HTML-to-Markdown. Preserves headings and links, loses element types and affordances.&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&lt;strong&gt;Firecrawl:&lt;/strong&gt; Readability + Markdown. Good for article extraction, blind to interactive elements.&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&lt;strong&gt;Jina Reader:&lt;/strong&gt; Custom extraction to Markdown. Similar tradeoffs to Firecrawl.&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&lt;strong&gt;AutoGPT:&lt;/strong&gt; Delegates to Jina/Firecrawl. Inherits their limitations.&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&lt;strong&gt;Browser Use:&lt;/strong&gt; Accessibility tree + DOM. Closest to structured, but designed for screen readers, not agents.&lt;/p&gt;
&lt;br&gt;
  &lt;p&gt;&lt;strong&gt;Stagehand:&lt;/strong&gt; Accessibility tree. Same verbose output issue as Browser Use.&lt;/p&gt;
&lt;br&gt;

&lt;h2&gt;
  
  
  The number that keeps me up at night
&lt;/h2&gt;

&lt;p&gt;Here is the calculation that I keep returning to. Take the WebTaskBench data: SOM uses 8,301 tokens per page on average. Raw HTML uses 33,181. The difference is 24,880 tokens.&lt;/p&gt;

&lt;p&gt;Multiply by 400 million pages per day. That is 9.95 trillion wasted tokens per day. Over a year, approximately 3.6 quadrillion tokens. At $0.75 per million tokens, that is $2.7 billion.&lt;/p&gt;

&lt;p&gt;But here is what keeps me up: that 400 million daily page fetch number is from our conservative bottom-up model, which only counts explicit user-triggered browsing. The top-down model calibrated against Cloudflare's total AI bot traffic suggests the real number could be 3 to 4x higher when you include autonomous agent workloads.&lt;/p&gt;

&lt;p&gt;And agent traffic is growing at 18 to 30% year over year, while LLM prices are dropping maybe 30 to 50% per generation (roughly every 6 to 12 months). The volume growth is outpacing the price decline. The total cost curve is going up.&lt;/p&gt;

&lt;p&gt;If current trends hold, by 2027 the annual waste could exceed $10 billion. Not because anyone is being negligent, but because the fundamental mismatch between the format the web serves (visual HTML) and the format agents need (structured semantic content) will become more expensive with every page added to the web and every new agent deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is a solvable problem
&lt;/h2&gt;

&lt;p&gt;I want to be clear about something: this is not a doom-and-gloom piece. The waste is real, the numbers are large, but the problem is entirely solvable with existing technology.&lt;/p&gt;

&lt;p&gt;The web has solved this exact problem before, for other consumer classes. When search engines emerged as a new web consumer in the late 1990s, they struggled with HTML too. The web responded by inventing sitemaps, robots.txt, and structured data (Schema.org, JSON-LD, OpenGraph). These machine-readable layers sit alongside the human-readable HTML and provide crawlers with the structured information they need without requiring them to parse visual markup.&lt;/p&gt;

&lt;p&gt;When applications emerged as a web consumer in the mid-2000s, the web responded again: REST APIs, GraphQL, webhooks. Purpose-built interfaces for programmatic consumption.&lt;/p&gt;

&lt;p&gt;AI agents are the fourth consumer of the web. They need the same thing: a purpose-built representation designed for their consumption model. Not raw HTML (too noisy), not plain text (too lossy), but something in between that preserves what agents need and discards what they do not.&lt;/p&gt;

&lt;p&gt;That is what we are building with Plasmate and the Semantic Object Model. But honestly, the specific technology matters less than the recognition that the problem exists and is getting more expensive every day. If someone builds a better solution than SOM, great. The industry still saves billions.&lt;/p&gt;

&lt;p&gt;The full analysis, including the complete estimation methodology, sensitivity analysis across pricing scenarios, and the framework survey data, is published as a &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;research paper&lt;/a&gt; on this site. If you work on agent infrastructure, pricing models, or web content delivery, I think you will find the data useful.&lt;/p&gt;

&lt;p&gt;What I really want this piece to leave you with is simpler than the math:&lt;br&gt;
That tax adds up to billions. It does not have to.&lt;br&gt;
&lt;em&gt;David Hurley is the founder of &lt;a href="https://plasmatelabs.com" rel="noopener noreferrer"&gt;Plasmate Labs&lt;/a&gt;. Previously, he founded Mautic, the world's first open source marketing automation platform. He writes at &lt;a href="https://dbhurley.com/blog" rel="noopener noreferrer"&gt;dbhurley.com/blog&lt;/a&gt; and publishes research at &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;dbhurley.com/papers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>What I Learned Building Mautic That Applies to the Agentic Web</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Mon, 30 Mar 2026 02:19:34 +0000</pubDate>
      <link>https://dev.to/dbhurley/what-i-learned-building-mautic-that-applies-to-the-agentic-web-2glj</link>
      <guid>https://dev.to/dbhurley/what-i-learned-building-mautic-that-applies-to-the-agentic-web-2glj</guid>
      <description>&lt;p&gt;In 2014, I started building Mautic. It became the world's first open source marketing automation platform. Acquia acquired it in 2019. Over those five years, I learned things about building infrastructure for a new class of consumer that I did not fully appreciate at the time.&lt;/p&gt;

&lt;p&gt;Now I am building Plasmate, an open source headless browser that compiles web pages into structured representations for AI agents. The domain is completely different. The technology is different. The users are different. But the structural problems, the adoption dynamics, and the strategic patterns are remarkably similar.&lt;/p&gt;

&lt;p&gt;This is what I learned building Mautic that applies directly to what I am building now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: Every new consumer class needs its own infrastructure
&lt;/h2&gt;

&lt;p&gt;When Mautic started, marketing automation existed. Marketo, HubSpot, Pardot, and Eloqua all served the enterprise. But they were closed, expensive, and inaccessible to the vast majority of organizations that needed marketing infrastructure.&lt;/p&gt;

&lt;p&gt;The insight was not that marketing automation was a new idea. The insight was that a large class of consumers (small and mid-market organizations, developers, agencies, nonprofits) had no infrastructure designed for their constraints. They needed marketing automation that was open, self-hostable, extensible, and free to start with.&lt;/p&gt;

&lt;p&gt;The same pattern is playing out with AI agents today. Web browsing infrastructure exists. Chrome, Playwright, Puppeteer, and Selenium all work. But they were designed for humans and human-oriented testing. AI agents are a different consumer class with different constraints: they need structured output (not pixels), token efficiency (not visual fidelity), semantic understanding (not DOM selectors), and speed at scale (not single-session debugging).&lt;/p&gt;

&lt;p&gt;The existing tools technically work for agents the same way enterprise marketing platforms technically worked for small businesses. But "technically works" and "designed for" are very different things. When I built Mautic, the opportunity was not inventing marketing automation. It was building marketing automation for the consumer class that existing tools underserved. With Plasmate, the opportunity is the same: building web browsing infrastructure for the consumer class that existing tools underserve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Open source is a distribution strategy, not a business model
&lt;/h2&gt;

&lt;p&gt;One of the most important things I learned at Mautic is that open source is how you get adopted, not how you get paid. These are related but distinct.&lt;/p&gt;

&lt;p&gt;Mautic grew to millions of installations because it was free and open. Organizations could try it without a sales call, deploy it without a procurement process, and extend it without permission. That distribution would have been impossible as a closed-source product competing against well-funded incumbents.&lt;/p&gt;

&lt;p&gt;But the business model was never "sell open source software." It was: build an open core that earns trust and adoption, then offer commercial services (hosting, support, premium features, integrations) to organizations that want operational convenience on top of the open foundation.&lt;/p&gt;

&lt;p&gt;I am applying the same structure to Plasmate. The compiler is open source (Apache 2.0). The SOM specification is open. The MCP server, browser extension, LangChain integration, LlamaIndex integration, and all SDKs are open. This is the distribution layer. Every developer who installs Plasmate and every agent framework that integrates SOM expands the ecosystem without a dollar of marketing spend.&lt;/p&gt;

&lt;p&gt;The commercial layer sits on top: SOM Cache (a shared semantic CDN for agents), Fleet Orchestration (managed browser infrastructure at scale), and enterprise services. These are operational conveniences that organizations will pay for once the open source foundation has earned their trust.&lt;/p&gt;

&lt;p&gt;The lesson from Mautic is that this sequence matters. Open source first, commercial second. Trust first, revenue second. If you reverse the order, you get neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: Standards and specifications matter more than implementations
&lt;/h2&gt;

&lt;p&gt;Mautic did not just build software. It contributed to the broader marketing technology ecosystem by establishing patterns that other tools adopted. The API structures, the campaign builder paradigm, the contact lifecycle model, the integration framework. These became conventions that outlasted any single implementation.&lt;/p&gt;

&lt;p&gt;I underestimated the importance of this at the time. I thought the code was the product. In retrospect, the specifications and patterns were at least as important. They shaped how an entire category of software was built, including by competitors.&lt;/p&gt;

&lt;p&gt;With Plasmate, I am investing in the specification layer from day one. The SOM Spec v1.0 is published as a formal document with a JSON Schema. The Agent Web Protocol (AWP) is a full protocol specification, not just an implementation. The robots.txt extension proposal follows RFC conventions. The &lt;code&gt;.well-known/som.json&lt;/code&gt; convention is designed as a web standard, not a Plasmate feature.&lt;/p&gt;

&lt;p&gt;This is deliberate. If SOM succeeds only as a Plasmate output format, it has failed. It needs to become a convention that any tool can produce and any agent can consume. That means the specification must be clear, open, and implementation-independent.&lt;/p&gt;

&lt;p&gt;I am also participating in the W3C Web Content for Browser and AI Community Group because I learned from Mautic that standards bodies matter even when they move slowly. Getting a seat at the table early means you can shape the conversation rather than react to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: The community decides whether you succeed
&lt;/h2&gt;

&lt;p&gt;Mautic's community was the product's greatest asset and its most demanding stakeholder. Contributors built integrations we never planned. Users found use cases we never imagined. And the community's expectations for openness, transparency, and quality pushed us to be better than we would have been alone.&lt;/p&gt;

&lt;p&gt;The same dynamics apply to Plasmate, but with an important difference. Mautic's community was primarily marketers and developers. Plasmate's community will be primarily AI agent developers and, eventually, web publishers. These are different audiences with different expectations.&lt;/p&gt;

&lt;p&gt;Agent developers care about reliability, speed, and token efficiency. They will adopt SOM if it measurably outperforms the alternatives on the metrics they track (cost per page, latency, task accuracy). The community will form around benchmark results and practical performance, not ideology.&lt;/p&gt;

&lt;p&gt;Publishers care about control, cost reduction, and future-proofing. They will adopt SOM-first serving if it reduces their infrastructure load from agent traffic and gives them control over how agents interpret their content. The community on this side will form around economic incentives.&lt;/p&gt;

&lt;p&gt;Building for both audiences simultaneously is harder than building for one. But the two-sided nature of the agentic web (agents consume, publishers serve) means that adoption on either side reinforces the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 5: Timing is the variable you control least and matters most
&lt;/h2&gt;

&lt;p&gt;Mautic launched in 2014. Marketing automation had existed since the mid-2000s. We were not early to the category. We were early to the open source version of the category. That timing turned out to be exactly right: the market was mature enough that organizations understood the value of marketing automation, but the open source alternative did not yet exist.&lt;/p&gt;

&lt;p&gt;If we had launched in 2008, the market would not have been ready. If we had launched in 2018, someone else would have done it first.&lt;/p&gt;

&lt;p&gt;I think about timing constantly with Plasmate. AI agents are browsing the web right now. Cloudflare reports that AI user-action crawling increased by over 15x in 2025 alone. The problem (agents consuming raw HTML) is real and growing. But the solution (structured representations as a web standard) requires adoption by both agent frameworks and web publishers. That adoption takes time.&lt;/p&gt;

&lt;p&gt;The question is whether we are building too early (before the ecosystem is ready to adopt) or at the right moment (when the pain is acute enough to drive change). Based on the trajectory of agent traffic and the increasing frustration of both agent developers (token costs) and publishers (crawl load without referral traffic), I believe the timing is right.&lt;/p&gt;

&lt;p&gt;But I learned from Mautic that you cannot force timing. You can only be ready when the moment arrives. That means having the specification published, the tools working, the integrations built, and the community seeded before the inflection point. When a major agent framework decides to adopt structured web representations as a default, we need to be the obvious choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 6: Acquisition is not the goal, but you should build as if it might happen
&lt;/h2&gt;

&lt;p&gt;Acquia acquired Mautic in 2019. That outcome was not the goal when we started. But the way we built Mautic (clean architecture, documented APIs, extensible framework, active community, clear licensing) made the acquisition possible and relatively smooth.&lt;/p&gt;

&lt;p&gt;I am building Plasmate with the same discipline. Clean separation between the open source compiler and the commercial services. Well-documented specifications. Clear licensing (Apache 2.0 for everything open). A codebase that another organization could adopt, extend, or integrate without depending on us.&lt;/p&gt;

&lt;p&gt;This is not because I expect or want an acquisition. It is because building with that level of rigor produces better software. If the code is clean enough for a stranger to understand, it is clean enough for your own team to maintain. If the specifications are clear enough for a competitor to implement, they are clear enough for your community to adopt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Looking back at a decade of building, I see a pattern that repeats:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new consumer class emerges that existing infrastructure underserves.&lt;/li&gt;
&lt;li&gt;Someone builds purpose-built infrastructure for that consumer class.&lt;/li&gt;
&lt;li&gt;Open source distribution earns trust and adoption faster than closed alternatives.&lt;/li&gt;
&lt;li&gt;Specifications and standards outlast implementations.&lt;/li&gt;
&lt;li&gt;Communities form around measurable performance, not ideology.&lt;/li&gt;
&lt;li&gt;Timing determines whether you are a pioneer (too early), a leader (right time), or a follower (too late).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Mautic, the new consumer was the small and mid-market organization that needed marketing automation. With Plasmate, the new consumer is the AI agent that needs structured web content.&lt;/p&gt;

&lt;p&gt;The domain is different. The pattern is identical. And the pattern works.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;David Hurley is the founder of &lt;a href="https://plasmatelabs.com" rel="noopener noreferrer"&gt;Plasmate Labs&lt;/a&gt; and the creator of Mautic, the world's first open source marketing automation platform. He writes about infrastructure, open source, and the agentic web at &lt;a href="https://dbhurley.com" rel="noopener noreferrer"&gt;dbhurley.com&lt;/a&gt;. Research papers are available at &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;dbhurley.com/papers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why I'm Building a Browser No Human Will Ever Use</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Mon, 30 Mar 2026 02:14:05 +0000</pubDate>
      <link>https://dev.to/dbhurley/why-im-building-a-browser-no-human-will-ever-use-5add</link>
      <guid>https://dev.to/dbhurley/why-im-building-a-browser-no-human-will-ever-use-5add</guid>
      <description>&lt;p&gt;I am building a browser that will never render a single pixel.&lt;/p&gt;

&lt;p&gt;No address bar. No tabs. No bookmarks. No window at all. Nobody will ever "open" Plasmate the way they open Chrome or Firefox or Safari. It has no visual interface because its consumer has no eyes.&lt;/p&gt;

&lt;p&gt;This sounds absurd until you think about who is actually browsing the web in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The absurd premise that turned out to be obvious
&lt;/h2&gt;

&lt;p&gt;When I first described Plasmate to other developers, the reaction was usually some version of: "Why not just use Playwright? Or Puppeteer? Or headless Chrome?" These are reasonable questions. They have headless modes. They can fetch pages and return HTML. The tools exist.&lt;/p&gt;

&lt;p&gt;But consider what headless Chrome actually does when you ask it to "browse" a page. It launches a full rendering engine. It constructs a layout tree. It calculates pixel positions for every element. It composites layers. It rasterizes text into bitmap glyphs. It computes box shadows, border radii, gradient interpolations, and subpixel antialiasing. Then, if you are using it for an AI agent, you throw all of that away and extract the text.&lt;/p&gt;

&lt;p&gt;This is like hiring a portrait painter to read you the newspaper. The painter's skills are extraordinary and completely irrelevant to the task.&lt;/p&gt;

&lt;p&gt;Chrome was designed to turn HTML into pixels for human eyes. Every architectural decision, every optimization, every feature in Chrome serves that purpose. When you repurpose it for AI agents, you are paying the full cost of pixel rendering for a consumer that will never see a pixel.&lt;/p&gt;

&lt;p&gt;The cost is not trivial. Chrome uses 200MB to 500MB of memory per page. It takes 1 to 3 seconds to render a complex page. At scale (an agent system monitoring hundreds of pages), this translates to gigabytes of RAM and minutes of compute spent on rendering that serves no purpose.&lt;/p&gt;

&lt;p&gt;The question is not "can Chrome work for agents?" The answer is obviously yes. The question is "should agents pay the cost of pixel rendering when they need text comprehension?" The answer is obviously no.&lt;/p&gt;

&lt;p&gt;That is why I am building a browser designed for a consumer that has no eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What agents actually need from a browser
&lt;/h2&gt;

&lt;p&gt;When I sat down to design Plasmate, I started by listing what an AI agent actually needs from a web browsing tool. The list was surprisingly short:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fetch the page.&lt;/strong&gt; Make an HTTP request, follow redirects, handle TLS, manage cookies. This is table stakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execute JavaScript.&lt;/strong&gt; Modern web pages are applications. The HTML document that arrives over the network is often a shell that loads and executes JavaScript to produce the actual content. An agent browser must execute this JavaScript to see what a human would see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understand the structure.&lt;/strong&gt; This is where every existing tool falls short. The agent does not need a pixel grid. It needs to know: what regions exist on this page (navigation, main content, sidebar, footer)? What elements are in each region? What type is each element (heading, paragraph, link, button, form field)? What can the agent do with each element (click, type, select, toggle)?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Produce structured output.&lt;/strong&gt; The agent's downstream consumer is a language model. The output must be structured, typed, and token-efficient. Raw HTML fails all three criteria.&lt;/p&gt;

&lt;p&gt;Notice what is not on this list: rendering pixels, computing layout, displaying fonts, animating transitions, playing audio or video, painting gradients, or any of the hundreds of other things a visual browser does. These capabilities represent the majority of Chrome's complexity and the majority of its resource consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture: what Plasmate actually does
&lt;/h2&gt;

&lt;p&gt;Plasmate is written in Rust. This was a deliberate choice for performance and memory safety, but also because Rust's ecosystem has excellent HTML parsing (html5ever, the same parser Firefox uses) and a mature V8 binding for JavaScript execution.&lt;/p&gt;

&lt;p&gt;The pipeline has five stages:&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Network fetch
&lt;/h3&gt;

&lt;p&gt;An HTTP client fetches the page with full TLS, redirect, and cookie support. This is straightforward and shared with every other browser. The difference is that Plasmate's HTTP client does not load CSS files, font files, or image files. It loads the HTML document and JavaScript files only. Everything visual is irrelevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: JavaScript execution
&lt;/h3&gt;

&lt;p&gt;The HTML document is parsed with html5ever, and JavaScript is executed via V8. This is necessary because many pages generate their content dynamically. React, Vue, Angular, and Next.js applications produce an empty &lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; in the initial HTML and populate it entirely through JavaScript.&lt;/p&gt;

&lt;p&gt;JavaScript execution is where most of the complexity lives. V8 is a large, sophisticated engine. But even V8's execution is faster and lighter than Chrome's full pipeline because we skip the rendering, layout, and painting phases that normally follow DOM construction.&lt;/p&gt;

&lt;p&gt;In v0.5.0, we added ICU data loading for Intl API support and raised script fetch limits to handle large SPA bundles (up to 3MB per script, 10MB total). We also added graceful degradation: when JavaScript execution fails, Plasmate compiles the pre-JavaScript HTML and returns partial SOM. Partial structured output is better than no output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Region detection
&lt;/h3&gt;

&lt;p&gt;Once the DOM is constructed (with JavaScript applied), Plasmate identifies semantic regions on the page. The detection uses a precedence chain:&lt;/p&gt;

&lt;p&gt;First, ARIA roles. If an element has &lt;code&gt;role="navigation"&lt;/code&gt; or &lt;code&gt;role="main"&lt;/code&gt;, that is definitive.&lt;/p&gt;

&lt;p&gt;Second, HTML5 landmark elements. &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; map directly to region roles.&lt;/p&gt;

&lt;p&gt;Third, class and ID heuristics. A &lt;code&gt;&amp;lt;div class="main-content"&amp;gt;&lt;/code&gt; is likely the main region. A &lt;code&gt;&amp;lt;div id="sidebar"&amp;gt;&lt;/code&gt; is likely an aside.&lt;/p&gt;

&lt;p&gt;Fourth, link density analysis. A container with many links and few other elements is likely navigation, even without explicit markup.&lt;/p&gt;

&lt;p&gt;Fifth, content heuristics. A container with copyright notices and privacy links is likely a footer.&lt;/p&gt;

&lt;p&gt;Sixth, fallback. Anything not assigned to a specific region goes into a generic "content" region.&lt;/p&gt;

&lt;p&gt;This detection produces a structured map of the page that no flat text extraction can replicate. An agent reading Plasmate output can go directly to the &lt;code&gt;main&lt;/code&gt; region without scanning the entire page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 4: Element classification
&lt;/h3&gt;

&lt;p&gt;Within each region, elements are classified by semantic role. Plasmate recognizes 15 element types: link, button, text_input, textarea, select, checkbox, radio, heading, image, list, table, paragraph, section, separator, and details (disclosure widgets).&lt;/p&gt;

&lt;p&gt;Each element receives:&lt;/p&gt;

&lt;p&gt;A stable identifier derived from SHA-256 hashing of the element's origin, role, accessible name, and DOM path. The same element on the same page always produces the same ID.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;html_id&lt;/code&gt; field preserving the original HTML &lt;code&gt;id&lt;/code&gt; attribute (when present), enabling agents to resolve back to the DOM for interaction.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;actions&lt;/code&gt; array declaring what the agent can do: click, type, clear, select, or toggle.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;attrs&lt;/code&gt; object with role-specific data: href for links, level for headings, options for selects, headers and rows for tables, open state and summary text for details widgets.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;aria&lt;/code&gt; sub-object capturing dynamic widget state: expanded, selected, checked, disabled, current, pressed, hidden.&lt;/p&gt;

&lt;p&gt;Semantic hints inferred from CSS class names: "primary," "danger," "disabled," "active." These are not visual styles but semantic signals that agents can use to understand element importance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 5: Serialization
&lt;/h3&gt;

&lt;p&gt;The classified regions and elements are serialized as JSON conforming to the SOM Spec v1.0. The output is deterministic: the same page always produces the same JSON (modulo dynamic content changes).&lt;/p&gt;

&lt;p&gt;The entire pipeline, from HTML string to SOM JSON, takes microseconds for the compilation step. The bottleneck is network fetch and JavaScript execution, not SOM compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means in practice
&lt;/h2&gt;

&lt;p&gt;The practical impact of this architecture is measurable. Across 50 real websites in our WebTaskBench evaluation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token reduction.&lt;/strong&gt; SOM uses 8,301 tokens per page on average versus 33,181 for raw HTML. That is a 4x reduction. For navigation-heavy pages, the ratio reaches 5.4x. For adversarial pages (heavy ads, cookie banners, JavaScript noise), it reaches 6.0x.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency improvement.&lt;/strong&gt; On Claude Sonnet 4, SOM is the fastest representation at 8.5 seconds average, compared to 16.2 seconds for HTML and 25.2 seconds for Markdown. Structured input reduces model reasoning time even compared to smaller unstructured input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory efficiency.&lt;/strong&gt; Plasmate uses approximately 30MB for 100 pages. Headless Chrome uses approximately 20GB for the same workload. The difference is the rendering pipeline that Plasmate skips entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; With daemon mode (a persistent process that keeps the browser warm), subsequent fetches complete in 200 to 400 milliseconds. Cold start is 2 to 3 seconds. This is competitive with simple HTTP-fetch-plus-readability tools while providing dramatically richer output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use Markdown?
&lt;/h2&gt;

&lt;p&gt;I get this question frequently, and it deserves a thorough answer.&lt;/p&gt;

&lt;p&gt;Markdown extraction (via tools like Jina Reader, Firecrawl, or basic readability libraries) is the most common alternative to raw HTML for agent consumption. It works well for text extraction tasks. In our benchmark, Markdown uses 4,542 tokens per page, which is smaller than SOM's 8,301.&lt;/p&gt;

&lt;p&gt;But Markdown has a fundamental limitation: it cannot represent interactivity. A Markdown document cannot tell an agent which text is a button, which is a link, which is a form field, or what actions are available. For an agent that needs to read an article and summarize it, this does not matter. For an agent that needs to fill a form, navigate a multi-step workflow, or click through search results, Markdown is blind.&lt;/p&gt;

&lt;p&gt;The latency data reinforces this. On Claude, Markdown is slower than SOM despite being smaller. Our interpretation is that Claude spends additional reasoning time trying to reconstruct page structure from ambiguous text. When the task requires understanding what is interactive and what is not, the model has to guess from context rather than reading explicit declarations.&lt;/p&gt;

&lt;p&gt;SOM occupies the middle ground: smaller than HTML, structured unlike Markdown, and fast for models to process because the semantic work is done at compile time rather than inference time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decisions that shaped the architecture
&lt;/h2&gt;

&lt;p&gt;Several architectural decisions in Plasmate deserve explanation because they diverge from what most people would expect from a browser project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Rust?
&lt;/h3&gt;

&lt;p&gt;The obvious choice for a browser project is C++ (what Chrome and Firefox use) or JavaScript/TypeScript (what most developer tools use). Rust is unusual.&lt;/p&gt;

&lt;p&gt;I chose Rust for three reasons. First, memory safety without garbage collection. A browser engine processes untrusted input (HTML, JavaScript, CSS) from arbitrary websites. Memory safety bugs in browser engines are the largest category of security vulnerabilities in Chrome and Firefox. Rust eliminates entire classes of these bugs at compile time.&lt;/p&gt;

&lt;p&gt;Second, performance. The SOM compilation pipeline processes every element in the DOM tree, computes SHA-256 hashes for stable IDs, runs heuristic analysis on class names and content patterns, and serializes the result as JSON. In Rust, this entire pipeline runs in microseconds per page. In a garbage-collected language, the memory allocation patterns would introduce pauses.&lt;/p&gt;

&lt;p&gt;Third, Rust's ecosystem has exactly the libraries needed. html5ever (the HTML parser from Mozilla's Servo project) and the V8 crate (Rust bindings for Google's JavaScript engine) provide production-quality foundations. The serde library provides zero-cost JSON serialization. These are not wrappers or bindings with impedance mismatches. They are native Rust libraries designed for high-performance text processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not use an existing rendering engine?
&lt;/h3&gt;

&lt;p&gt;Blink (Chrome's rendering engine) and Gecko (Firefox's) are extraordinary pieces of engineering. They handle every edge case in CSS layout, every quirk in the HTML specification, and every performance optimization needed for smooth visual rendering.&lt;/p&gt;

&lt;p&gt;But they are designed around a fundamental assumption: the output is a pixel grid on a screen. Every data structure, every caching strategy, every parallelization decision in these engines optimizes for that output target. Repurposing them for structured text output means carrying all of that complexity while using none of it.&lt;/p&gt;

&lt;p&gt;Plasmate uses html5ever for DOM construction and V8 for JavaScript execution, but it builds its own pipeline for everything after that. Region detection, element classification, stable ID generation, ARIA state capture, and SOM serialization are all custom. This is not because existing engines cannot do these things. It is because doing them well requires different architectural assumptions than visual rendering demands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is the output JSON and not something else?
&lt;/h3&gt;

&lt;p&gt;SOM is serialized as JSON because JSON is the lingua franca of agent frameworks. Every programming language can parse JSON. Every LLM API accepts text that includes JSON. Every agent framework stores and transmits structured data as JSON.&lt;/p&gt;

&lt;p&gt;We considered alternatives. Protocol Buffers would be smaller on the wire but harder for agents to read inline. XML would be semantically richer but token-heavy. YAML would be human-readable but ambiguous. MessagePack would be compact but binary.&lt;/p&gt;

&lt;p&gt;JSON won because the primary consumer is a language model reading text. The JSON representation of a SOM document is directly readable by the model as context. No deserialization step is needed. The model sees the structure, the types, and the values in the same stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contrarian bet
&lt;/h2&gt;

&lt;p&gt;Building Plasmate required a contrarian belief: that the web needs a new browser, not a new wrapper around Chrome.&lt;/p&gt;

&lt;p&gt;The wrapper approach is popular. Playwright, Puppeteer, Browserbase, Steel, and many others wrap Chrome and add convenience APIs on top. This works, and it is a reasonable strategy for tools that need pixel-perfect browser fidelity.&lt;/p&gt;

&lt;p&gt;But wrapping Chrome means accepting Chrome's architectural assumptions. You pay for pixel rendering even when you do not need pixels. You accept Chrome's memory model, Chrome's process architecture, Chrome's security sandbox, and Chrome's update cycle. These are excellent design decisions for a visual browser. They are unnecessary constraints for an agent browser.&lt;/p&gt;

&lt;p&gt;Plasmate does not wrap Chrome. It uses the same HTML parser (html5ever, from Mozilla) and the same JavaScript engine (V8, from Google), but it constructs its own pipeline around them. The pipeline is designed for a specific consumer (AI agents) with specific needs (structured output, token efficiency, semantic understanding) that Chrome's pipeline was never intended to serve.&lt;/p&gt;

&lt;p&gt;This is the same bet I made with Mautic. The marketing automation tools existed (Marketo, HubSpot, Pardot). But they were designed for a different consumer with different constraints. Building for the underserved consumer, rather than wrapping the existing tools, produced a fundamentally better product for that audience.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;Plasmate today handles the compilation side: HTML in, SOM out. The next challenges are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript coverage.&lt;/strong&gt; Some heavily dynamic sites still fail during JavaScript execution. Khan Academy, certain React applications with complex hydration, and sites with aggressive anti-bot measures. Each failure is a reason for an agent to fall back to a simpler tool. Closing these gaps is engineering work, not architectural work, and it is ongoing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WASM distribution.&lt;/strong&gt; We recently published the SOM compiler as a WebAssembly module (&lt;code&gt;npm install plasmate-wasm&lt;/code&gt;). This allows SOM compilation in any JavaScript runtime without a native binary. It is the first step toward making Plasmate's compilation available everywhere JavaScript runs: serverless functions, edge workers, browsers, CI pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Publisher adoption.&lt;/strong&gt; The &lt;code&gt;plasmate compile&lt;/code&gt; command accepts HTML from files or stdin without any network requests. Publishers can integrate SOM generation into their build pipelines and serve structured representations alongside HTML. Six properties already do this. Growing that number is as important as improving the compiler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standards.&lt;/strong&gt; The SOM Spec, the Agent Web Protocol, and the robots.txt extension proposal are all published openly. We are participating in the W3C Community Group for Web Content and Browser AI. If SOM becomes a web standard rather than a Plasmate feature, the entire ecosystem benefits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;I am building a browser no human will ever use because humans are no longer the only consumers of the web. AI agents browse billions of pages per day, and every one of those pages is served in a format designed for human eyes. The waste is staggering: we estimated in a recent paper that HTML presentation noise costs the agent ecosystem $1 billion to $5 billion per year in unnecessary token consumption.&lt;/p&gt;

&lt;p&gt;The solution is not to make agents better at reading HTML. The solution is to give agents a format designed for them, the same way the web gave search engines sitemaps and applications gave consumers APIs.&lt;/p&gt;

&lt;p&gt;Plasmate is that format's compiler. A browser built for a consumer that will never see a pixel, because it does not need to.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;David Hurley is the founder of &lt;a href="https://plasmatelabs.com" rel="noopener noreferrer"&gt;Plasmate Labs&lt;/a&gt;. Previously, he founded Mautic, the world's first open source marketing automation platform. He writes at &lt;a href="https://dbhurley.com/blog" rel="noopener noreferrer"&gt;dbhurley.com/blog&lt;/a&gt; and publishes research at &lt;a href="https://dbhurley.com/papers" rel="noopener noreferrer"&gt;dbhurley.com/papers&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Web's Fourth State</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Mon, 30 Mar 2026 02:13:56 +0000</pubDate>
      <link>https://dev.to/dbhurley/the-webs-fourth-state-38ok</link>
      <guid>https://dev.to/dbhurley/the-webs-fourth-state-38ok</guid>
      <description></description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Plasmate SOM Compiler Now Available as WebAssembly</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Sun, 29 Mar 2026 17:07:59 +0000</pubDate>
      <link>https://dev.to/dbhurley/plasmate-som-compiler-now-available-as-webassembly-4al7</link>
      <guid>https://dev.to/dbhurley/plasmate-som-compiler-now-available-as-webassembly-4al7</guid>
      <description>&lt;p&gt;One of the most common objections to adopting Plasmate has been binary distribution. The full Plasmate CLI is a compiled Rust binary that includes a browser engine for JavaScript execution and page rendering. It works well, but it requires platform-specific binaries and cannot run in environments that restrict native code execution (serverless functions, edge workers, browser contexts).&lt;/p&gt;

&lt;p&gt;The SOM compiler itself has no such limitation. It is pure computation: parse HTML with html5ever, walk the DOM tree, identify semantic regions, classify elements, generate stable IDs, serialize JSON. No system calls, no network, no file system. Just a function that takes a string and returns a string.&lt;/p&gt;

&lt;p&gt;Today we are publishing that function as WebAssembly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and use
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;plasmate-wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;compile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plasmate-wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;nav&amp;gt;&amp;lt;a href="/about"&amp;gt;About&amp;lt;/a&amp;gt;&amp;lt;/nav&amp;gt;&amp;lt;main&amp;gt;&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;World&amp;lt;/p&amp;gt;&amp;lt;/main&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;som&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;som&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// "Hello"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;som&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// 2 (navigation + main)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;som&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;compile&lt;/code&gt; function takes two arguments: an HTML string and a URL for stable ID generation. No network request is made. It returns a SOM JSON string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this runs
&lt;/h2&gt;

&lt;p&gt;The WASM module works in any JavaScript runtime that supports WebAssembly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node.js, Deno, Bun:&lt;/strong&gt; Import as a regular npm package. The WASM binary is loaded automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browsers:&lt;/strong&gt; Use the ESM build (available in the pkg-web directory). Useful for client-side SOM generation in developer tools or browser extensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless (AWS Lambda, Vercel Functions, Cloudflare Workers):&lt;/strong&gt; The 380KB gzipped package size fits within typical size limits. No native binary installation needed during deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge (Cloudflare Workers, Deno Deploy, Vercel Edge Runtime):&lt;/strong&gt; WASM is a first-class citizen in edge runtimes. The compiler initializes in milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Size and performance
&lt;/h2&gt;

&lt;p&gt;The WASM binary is 864KB uncompressed, 380KB after gzip. For comparison, the full Plasmate binary is approximately 30MB.&lt;/p&gt;

&lt;p&gt;Compilation speed is comparable to the native binary for the compile step itself. The native binary is faster overall because it avoids WASM interpreter overhead, but the difference is small (microseconds per page for the compile step). The bottleneck in the full pipeline has always been fetching and JavaScript execution, not SOM compilation.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use WASM vs the full CLI
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fetching live pages with JS execution&lt;/td&gt;
&lt;td&gt;Full CLI (&lt;code&gt;plasmate fetch&lt;/code&gt;) or daemon mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiling HTML you already have&lt;/td&gt;
&lt;td&gt;WASM (&lt;code&gt;plasmate-wasm&lt;/code&gt;) or CLI (&lt;code&gt;plasmate compile&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serverless or edge deployment&lt;/td&gt;
&lt;td&gt;WASM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser extension or developer tool&lt;/td&gt;
&lt;td&gt;WASM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD pipeline (HTML already rendered)&lt;/td&gt;
&lt;td&gt;Either (WASM avoids binary installation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publisher build pipeline&lt;/td&gt;
&lt;td&gt;Either (WASM is simpler to integrate)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key distinction: if you need to fetch a live page and execute its JavaScript, you need the full CLI. If you already have the HTML (from your CMS, build pipeline, or another HTTP client), the WASM compiler does everything you need with zero native dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publisher integration example
&lt;/h2&gt;

&lt;p&gt;A static site generator that produces SOM during the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;compile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plasmate-wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// After Hugo/Astro/Next.js has rendered HTML to disk&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;somDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/.well-known/som&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;somDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;index&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;html$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;html$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://mysite.com/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;somJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;somDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;somJson&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs entirely at build time with no network requests and no native binary. The SOM files are deployed alongside the HTML as static assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare Worker example
&lt;/h2&gt;

&lt;p&gt;An edge function that compiles HTML to SOM on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;compile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plasmate-wasm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing url parameter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch the page&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Compile to SOM&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;somJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;somJson&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a lightweight SOM proxy running at the edge. No Plasmate binary deployment needed. The WASM module handles everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;We plan to publish the WASM compiler to additional package registries (Deno modules, JSR) and create framework-specific wrappers for popular static site generators. The long-term goal is to make SOM compilation available everywhere JavaScript runs.&lt;/p&gt;

&lt;p&gt;For sites that require JavaScript execution to render content (SPAs, dynamically loaded data), the full CLI and daemon mode remain necessary. We are actively improving JS coverage for those cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/plasmate-labs/plasmate-wasm" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://www.npmjs.com/package/plasmate-wasm" rel="noopener noreferrer"&gt;npm&lt;/a&gt; | &lt;a href="https://github.com/plasmate-labs/plasmate" rel="noopener noreferrer"&gt;Full CLI&lt;/a&gt; | &lt;a href="https://docs.plasmate.app" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>HTML vs Markdown vs SOM: Which Format Should Your AI Agent Use?</title>
      <dc:creator>David Hurley</dc:creator>
      <pubDate>Sat, 28 Mar 2026 13:39:34 +0000</pubDate>
      <link>https://dev.to/dbhurley/html-vs-markdown-vs-som-which-format-should-your-ai-agent-use-2aad</link>
      <guid>https://dev.to/dbhurley/html-vs-markdown-vs-som-which-format-should-your-ai-agent-use-2aad</guid>
      <description>&lt;p&gt;Every AI agent that browses the web faces the same question: how do you represent a web page to a language model?&lt;/p&gt;

&lt;p&gt;The default answer, raw HTML, is expensive and slow. A typical page dumps 30,000+ tokens into your context window, most of it CSS classes and layout divs. But what are the actual alternatives? And do they work?&lt;/p&gt;

&lt;p&gt;We ran WebTaskBench, 100 tasks across GPT-4o and Claude Sonnet 4, to find out. The results surprised us.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Representations
&lt;/h2&gt;

&lt;p&gt;When an agent needs to understand a web page, there are three common approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Raw HTML
&lt;/h3&gt;

&lt;p&gt;The DOM as-is. Every &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, every &lt;code&gt;class="sc-1234 flex items-center gap-2"&lt;/code&gt;, every inline script. This is what most agents send today.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sc-1234 flex items-center gap-2 px-4 py-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/about"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-500 hover:underline
     font-medium tracking-tight text-sm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-400"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;|&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/pricing"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-500 hover:underline
     font-medium tracking-tight text-sm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Pricing&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Complete fidelity to the DOM. No information lost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; 80-95% of tokens are noise (styling, scripts, tracking). Expensive. Slow.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Markdown
&lt;/h3&gt;

&lt;p&gt;Strip the HTML to readable text, preserving structure through Markdown conventions. This is what tools like Jina Reader and many scraping libraries produce.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;About&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;/about&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; | &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Pricing&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;/pricing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Dramatically fewer tokens. Human-readable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Loses interactive elements. No way to know what's clickable. Navigation tasks become guesswork.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SOM (Semantic Object Model)
&lt;/h3&gt;

&lt;p&gt;A structured JSON representation that preserves meaning and interactivity while stripping presentation noise. Each element includes its semantic role and available actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"navigation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"elements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"link"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"About"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e_a1b2c3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"attrs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"href"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/about"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"link"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pricing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e_d4e5f6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"attrs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"href"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/pricing"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"actions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"click"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Minimal tokens. Preserves interactivity. Clear semantic roles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Requires a SOM-aware fetcher (like &lt;a href="https://plasmate.app" rel="noopener noreferrer"&gt;Plasmate&lt;/a&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Token Cost Comparison
&lt;/h2&gt;

&lt;p&gt;We measured input tokens across 50 web pages (news sites, documentation, e-commerce, government sites, social platforms). The differences are stark:&lt;/p&gt;

&lt;p&gt;Token Cost Comparison:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML: 33,181 average input tokens (1.0x)&lt;/li&gt;
&lt;li&gt;SOM: 8,301 average input tokens (4.0x fewer)&lt;/li&gt;
&lt;li&gt;Markdown: 4,542 average input tokens (7.3x fewer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Markdown wins on raw token count, it strips everything. But tokens aren't the whole story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Per 1,000 Pages (at $3/M input tokens)
&lt;/h3&gt;

&lt;p&gt;Cost Per 1,000 Pages (at $3/M input tokens):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML: $99.54 (baseline)&lt;/li&gt;
&lt;li&gt;SOM: $24.90 (75% savings)&lt;/li&gt;
&lt;li&gt;Markdown: $13.63 (86% savings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're just extracting text, Markdown is cheaper. But if your agent needs to &lt;em&gt;interact&lt;/em&gt; with pages, click buttons, fill forms, navigate, Markdown falls apart.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Latency Surprise
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. We expected Markdown to be fastest (fewest tokens = fastest inference). That's true for GPT-4o:&lt;/p&gt;

&lt;h3&gt;
  
  
  GPT-4o Latency (seconds)
&lt;/h3&gt;

&lt;p&gt;GPT-4o Latency (seconds):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML: 2.7s&lt;/li&gt;
&lt;li&gt;Markdown: 1.9s&lt;/li&gt;
&lt;li&gt;SOM: 1.4s (fastest)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SOM beats both. Why? Two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structured input parses faster.&lt;/strong&gt; JSON with clear roles lets the model skip the "what is this?" step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less ambiguity = shorter reasoning chains.&lt;/strong&gt; When a link is explicitly marked &lt;code&gt;"role": "link", "actions": ["click"]&lt;/code&gt;, the model doesn't need to infer interactivity from context.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Claude Sonnet 4 Latency (seconds)
&lt;/h3&gt;

&lt;p&gt;Claude Sonnet 4 Latency (seconds):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML: 16.2s&lt;/li&gt;
&lt;li&gt;Markdown: 25.2s (slowest)&lt;/li&gt;
&lt;li&gt;SOM: 8.5s (fastest)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait, Markdown is &lt;em&gt;slower&lt;/em&gt; than HTML on Claude? Yes. And SOM is nearly 3x faster than Markdown.&lt;/p&gt;

&lt;p&gt;Claude appears to struggle with ambiguous Markdown when the task requires understanding page structure. The model spends more time reasoning about what elements are clickable, what actions are available, and how to express those actions. With SOM, that information is explicit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category Breakdown
&lt;/h2&gt;

&lt;p&gt;Not all tasks are equal. We tested extraction, comparison, navigation, summarization, and adversarial tasks (noisy pages with heavy chrome).&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML/SOM Token Ratio by Category
&lt;/h3&gt;

&lt;p&gt;HTML/SOM Token Ratio by Category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extraction: 2.2x (SOM wins, but margin is smaller)&lt;/li&gt;
&lt;li&gt;Comparison: 3.9x (Multi-item pages benefit from structure)&lt;/li&gt;
&lt;li&gt;Summarization: 3.9x (Similar to comparison)&lt;/li&gt;
&lt;li&gt;Navigation: 5.4x (Interactivity data is dense in SOM)&lt;/li&gt;
&lt;li&gt;Adversarial: 6.0x (Anti-bot clutter inflates HTML massively)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For adversarial pages (cookie banners, heavy JavaScript, ad-filled layouts), HTML explodes with noise while SOM stays lean. The 6x ratio means you're paying 6x more for HTML on the hardest pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Markdown Fails
&lt;/h3&gt;

&lt;p&gt;Markdown works great for "read this article and summarize it." It breaks down for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Form filling&lt;/strong&gt;: Markdown can't represent input fields, dropdowns, or submit buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation&lt;/strong&gt;: No reliable way to know which text is a clickable link vs decorative&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateful interactions&lt;/strong&gt;: Multi-step flows (add to cart, checkout) require element references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic content&lt;/strong&gt;: JavaScript-rendered content often doesn't survive text conversion&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When to Use What
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Markdown when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pure text extraction (summarize this article)&lt;/li&gt;
&lt;li&gt;No interaction needed&lt;/li&gt;
&lt;li&gt;Budget is the only constraint&lt;/li&gt;
&lt;li&gt;You control the source (your own docs, known-good pages)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use SOM when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Agents need to click, type, or navigate&lt;/li&gt;
&lt;li&gt;Multi-step workflows&lt;/li&gt;
&lt;li&gt;Unknown or adversarial pages&lt;/li&gt;
&lt;li&gt;Latency matters (SOM is fastest on both models)&lt;/li&gt;
&lt;li&gt;You want consistent structure across diverse sites&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use HTML when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need pixel-perfect DOM fidelity&lt;/li&gt;
&lt;li&gt;Building a browser automation tool that maps directly to CSS selectors&lt;/li&gt;
&lt;li&gt;Debugging what the page actually contains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest recommendation: &lt;strong&gt;default to SOM&lt;/strong&gt; unless you have a specific reason not to. It's faster, cheaper than HTML, and handles interactive tasks that Markdown can't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with Plasmate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/plasmate-labs/plasmate" rel="noopener noreferrer"&gt;Plasmate&lt;/a&gt; is the reference implementation of SOM. Three ways to use it:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; plasmate
plasmate fetch https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. MCP Server (Claude Desktop / Cursor)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"plasmate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plasmate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. SOM Cache API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cache.plasmate.app/v1/som&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://news.ycombinator.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;som&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For authenticated browsing (sites that require login), see the &lt;a href="https://docs.plasmate.app/guide-authenticated-browsing" rel="noopener noreferrer"&gt;Authenticated Browsing Guide&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Data
&lt;/h2&gt;

&lt;p&gt;All numbers in this post come from &lt;a href="https://github.com/plasmate-labs/plasmate/tree/master/benchmarks/webtaskbench" rel="noopener noreferrer"&gt;WebTaskBench&lt;/a&gt;, an open benchmark of 100 web tasks across 50 real-world URLs. You can run it yourself and reproduce every number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.plasmate.app/som-spec" rel="noopener noreferrer"&gt;SOM Spec v1.0&lt;/a&gt;, The complete specification&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.plasmate.app/som-first-sites" rel="noopener noreferrer"&gt;SOM-first Websites&lt;/a&gt;, How publishers can serve SOM natively&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/plasmate-labs/langchain-plasmate" rel="noopener noreferrer"&gt;LangChain integration&lt;/a&gt;, Use SOM in LangChain pipelines&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/plasmate-labs/plasmate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, Star us if this was useful&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/plasmate" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, &lt;code&gt;npm install -g plasmate&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
