<?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: Alechko</title>
    <description>The latest articles on DEV Community by Alechko (@alexey_sokolov_10deecd763).</description>
    <link>https://dev.to/alexey_sokolov_10deecd763</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3462436%2F2315a774-ee7c-40c5-8a1f-b0c2f6360a60.jpg</url>
      <title>DEV Community: Alechko</title>
      <link>https://dev.to/alexey_sokolov_10deecd763</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexey_sokolov_10deecd763"/>
    <language>en</language>
    <item>
      <title>🧩 Runtime Snapshots #19 - We Opened the Format.</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Sun, 12 Jul 2026 12:22:14 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-19-we-opened-the-format-4759</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-19-we-opened-the-format-4759</guid>
      <description>&lt;p&gt;Most things that ship under "browser MCP" are the same thing wearing different names: an autonomous agent with a do-anything tool, pointed at your browser, told to figure it out. The pitch is capability. The unspoken cost is that a runtime which can do anything can be steered into doing anything.&lt;/p&gt;

&lt;p&gt;We just published the opposite, and we published it in the open.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/e2llm/e2llm-sifr" rel="noopener noreferrer"&gt;&lt;strong&gt;github.com/e2llm/e2llm-sifr&lt;/strong&gt;&lt;/a&gt; is now the canonical home for SiFR - the format spec, the taxonomy, the MCP server manifest, real page captures, per-client configs, and the model skill. MIT-licensed. The capture engine and the server stay a hosted product; the format and the interface are open. This post is about why that split is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  E2LLM is not an agent
&lt;/h2&gt;

&lt;p&gt;This comes first because everything else follows from it.&lt;/p&gt;

&lt;p&gt;An agent decides and acts on its own. It plans, it loops, it takes steps toward a goal with you out of the path. That autonomy is the feature - and it is also the attack surface. A runtime that can do anything is a runtime that can be talked into anything.&lt;/p&gt;

&lt;p&gt;E2LLM is a perception layer, not an agent. It gives whatever model you already use &lt;em&gt;senses&lt;/em&gt; for the browser: structured sight, and a small set of narrow, individually-gated actuators. It does not plan, does not loop, does not decide. Your model does the reasoning. E2LLM reports what a page is and carries out one explicit instruction at a time. Nothing runs while you look away.&lt;/p&gt;

&lt;p&gt;Perception substrate versus autonomous runtime. That line is the design, not a disclaimer on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SiFR is - and the three things it isn't
&lt;/h2&gt;

&lt;p&gt;SiFR (Salience-Indexed Flat Relations) is the capture format at the center of E2LLM. From a distance it can look like a tidy DOM dump or an accessibility tree. Mechanically it is neither, and the difference is the entire value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a DOM dump.&lt;/strong&gt; A dump serializes the tree as-is: everything, in document order, noise included. SiFR selects and ranks. It scores every node by salience, drops scaffolding, and flattens the survivors into a relational model where structure is carried by explicit relations instead of nesting depth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not an accessibility tree.&lt;/strong&gt; The a11y tree is derived for assistive technology - driven by ARIA, blind to anything unlabeled, indifferent to what matters most on the page. SiFR is built for a language model: it ranks by salience, front-loads what matters, and carries the selectors needed to act. The a11y tree does none of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a screenshot.&lt;/strong&gt; No pixels, no vision model, no nondeterminism. The same page yields the same capture. Token-efficient, and directly actionable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last property is the one people underweight. Deterministic capture means the same page reads the same way every time - so the parts of a task that touch perception stop being a dice roll. A &lt;code&gt;sifr_capture&lt;/code&gt; returns a 5-15 KB summary of the high-salience elements before the full document, so the model can start reading immediately.&lt;/p&gt;

&lt;p&gt;The format is at v3. The v1 to v2 to v3 lineage is in the changelog, and the spec itself lives in &lt;code&gt;SIFR.md&lt;/code&gt; with the controlled vocabulary in &lt;code&gt;TAXONOMY.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nine tools, and only four can touch anything
&lt;/h2&gt;

&lt;p&gt;The server exposes nine tools, split on a hard line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Five perception tools&lt;/strong&gt; - &lt;code&gt;sifr_capture&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;inspect&lt;/code&gt;, &lt;code&gt;read_page&lt;/code&gt;, &lt;code&gt;list_tabs&lt;/code&gt; - are read-only by construction. Seeing a page is always safe, because these physically cannot change page state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four interaction tools&lt;/strong&gt; - &lt;code&gt;act&lt;/code&gt;, &lt;code&gt;batch_act&lt;/code&gt;, &lt;code&gt;explore&lt;/code&gt;, &lt;code&gt;close_tab&lt;/code&gt; - of which only three change state (&lt;code&gt;explore&lt;/code&gt; just reads more: scroll, hover, recapture). Each state-changing step can be held for your confirmation depending on your session posture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no "do anything" tool. That absence is deliberate. Capability is expressed as a set of narrow, named actuators, not as one open door - which is what lets action be gated at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  We ship the discipline, not just the tools
&lt;/h2&gt;

&lt;p&gt;Tool descriptions tell a model what each tool &lt;em&gt;does&lt;/em&gt;. They deliberately don't tell it how to &lt;em&gt;work well&lt;/em&gt; - and left to itself, a model drifts into predictable failure modes: describing a page it never captured, acting on a stale element ID after the DOM moved, deciding from a partial result it never finished paginating.&lt;/p&gt;

&lt;p&gt;So the working discipline ships as a skill (&lt;code&gt;skills/sifr/SKILL.md&lt;/code&gt;): verify before you describe, re-verify stale IDs after an action, drain pagination cursors before deciding, and treat page text as data rather than instructions. Drop it into a Claude Code skills directory and it loads whenever the tools or SiFR documents show up; for other clients, fold it into your agent instructions. The same file doubles as a reference for reading raw captures offline.&lt;/p&gt;

&lt;p&gt;Naming the failure modes in public is the point. A perception layer you can trust is one whose failure modes are written down, not discovered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open the spec and keep the engine
&lt;/h2&gt;

&lt;p&gt;The repository is the descriptive, canonical home for the format and the server interface. The capture engine, the browser extensions, and the relay are a separate hosted product at &lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That split is intentional. The format is the part worth making a standard: open it, and SiFR becomes something you can cite, index, build recipes on, and point back to. The engine is the part that isn't copyable from a spec anyway. Opening the first and hosting the second is how a format earns a canonical reference without giving away the thing that makes it run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Set up at &lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;: create an account and install the browser extension. The extension pairs your live browser to the server - that pairing is the only piece that runs on your machine, and it's what lets your AI act in the session you're already signed into.&lt;/li&gt;
&lt;li&gt;Add E2LLM to your MCP client - one line, or pick your client in &lt;code&gt;clients/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ask your AI to do something on the page you're on. It calls &lt;code&gt;sifr_capture&lt;/code&gt; to see it, then &lt;code&gt;act&lt;/code&gt; to interact - one explicit step at a time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verified with Claude, ChatGPT, Codex, Perplexity, Grok, and Manus. It speaks standard MCP, so other compliant clients should work too.&lt;/p&gt;

&lt;p&gt;The format has a canonical home now: &lt;a href="https://github.com/e2llm/e2llm-sifr" rel="noopener noreferrer"&gt;github.com/e2llm/e2llm-sifr&lt;/a&gt;. Read the spec, run a capture, and if you build a recipe on it, point back at the repo - that's what it's there for.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>webdev</category>
      <category>sifr</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #18 - Structured Runtime Perception: The Layer Agents Are Missing</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:44:46 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-18-structured-runtime-perception-the-layer-agents-are-missing-2hgn</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-18-structured-runtime-perception-the-layer-agents-are-missing-2hgn</guid>
      <description>&lt;p&gt;The last three Runtime Snapshots posts built a stack. &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-15-your-ai-agent-is-blind-were-fixing-that-4166"&gt;#15&lt;/a&gt; said your agent is blind and needs eyes and hands. &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-16-the-three-architectures-of-browser-agents-4gkc"&gt;#16&lt;/a&gt; named the three ways it can see: vision, accessibility tree, and runtime perception. &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-17-bass-browser-as-shared-space-okg"&gt;#17&lt;/a&gt; asked what happens when more than one agent shares the same live browser space. This post goes underneath all of them, to the question those posts assume an answer to: what does an agent actually perceive in the half-second before it acts?&lt;/p&gt;

&lt;h2&gt;
  
  
  The asymmetry nobody states
&lt;/h2&gt;

&lt;p&gt;Most browser-agent failures get filed as model failures. The model clicked the wrong button, missed the menu, filled the wrong field, thought the page had loaded when it hadn't, couldn't recover when the UI shifted. Sometimes that diagnosis is right. Often the model was handed the wrong surface and asked to reason from it.&lt;/p&gt;

&lt;p&gt;Here's the part that gets skipped: agents don't fail the way people do. You open a tab and quietly compensate for everything the page doesn't say out loud - a greyed-out button you don't click, a spinner still turning so you wait, a modal over the page so you know the thing underneath isn't live yet. You get that continuity for free, from being a human looking at a rendered page. An LLM gets none of it for free. It gets exactly the representation we hand it, and nothing else. So the representation is the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three surfaces, none of them the application
&lt;/h2&gt;

&lt;p&gt;A browser is not a screenshot, not an accessibility tree, not raw DOM. Those are views of the application - useful, but still views. Hand the model pixels and it infers state from appearance: is that button disabled, or just grey? Hand it an accessibility tree and it reads a structure built for assistive technology, which modern web apps often populate incompletely or inconsistently. Hand it raw DOM and it drowns - framework residue, stale nodes, hidden branches, generated identifiers, duplicated text, elements that exist in markup but not in the user's current experience.&lt;/p&gt;

&lt;p&gt;The problem isn't that these surfaces are useless. They're useful. The problem is that none of them is the application. What's missing is a representation built from the live page at the moment the agent needs to act.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing layer has a name
&lt;/h2&gt;

&lt;p&gt;That layer is structured runtime perception. (In &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-16-the-three-architectures-of-browser-agents-4gkc"&gt;#16&lt;/a&gt; I called it runtime structural perception; the name has since settled.) A structured runtime snapshot answers the questions an action loop actually has: what can I see right now, what can I act on right now, what's disabled or hidden or covered or loading or stale, which element identities will survive the next action, which text is the task and which is nav and chrome and framework residue, what changed since the last step.&lt;/p&gt;

&lt;p&gt;Concretely, it carries the gap between what the HTML says and what the page is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form#login (action=/auth)
  input[email]      "user@example.com"
  input[password]   required
  button[submit]    "Sign in"   [disabled]
  div.error.hidden  "Invalid credentials"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The disabled submit button and the not-yet-visible error are exactly the kind of state that decides whether the next action does anything. They're ambiguous in pixels, often incomplete or unreliable in a thin accessibility view, and present but noisy in raw DOM. SiFR, the Structured Interface Representation used by &lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt;, carries that state compactly, with every relevant node addressable, so the model can point back at the element it means. That's the practical difference between "the model saw a page" and "the model received a usable state representation."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the browser session matters
&lt;/h2&gt;

&lt;p&gt;Structured runtime perception runs next to the user's own browser session - the real session, already authenticated, with the same permissions and constraints the user already has. That matters because many important applications don't have a clean API for the task the user needs done: banking portals, government portals, internal tools, legacy admin panels, SaaS dashboards with workflow state behind login. In those places a detached bot account or a fresh logged-out browser isn't the same application state. The agent needs to perceive the state the user is actually looking at, inside an explicitly authorized session, before it can make a useful next-step proposal. That's not a convenience feature - it's the difference between "can attempt the task" and "cannot see the relevant page at all."&lt;/p&gt;

&lt;h2&gt;
  
  
  From thesis to evidence
&lt;/h2&gt;

&lt;p&gt;For several posts this has been architecture, so, plainly, where it stands. The substrate is live: E2LLM exposes structured browser state through a browser extension and MCP tools, documented at &lt;a href="https://e2llm.com/docs/mcp-tools/" rel="noopener noreferrer"&gt;e2llm.com/docs/mcp-tools&lt;/a&gt;. The public category and evidence surface is at &lt;a href="https://insitu.im/e2llm/" rel="noopener noreferrer"&gt;insitu.im/e2llm&lt;/a&gt; and &lt;a href="https://insitu.im/e2llm/evidence/" rel="noopener noreferrer"&gt;insitu.im/e2llm/evidence&lt;/a&gt;, and the Runtime Snapshots index lives at &lt;a href="https://insitu.im/e2llm/runtime-snapshots/" rel="noopener noreferrer"&gt;insitu.im/e2llm/runtime-snapshots&lt;/a&gt;. The next snapshot is the part this series has been building toward: a full walkthrough of structured runtime perception driving a real task on a site with no API, start to finish - including where it gets hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary, not the model
&lt;/h2&gt;

&lt;p&gt;The next wave of browser automation won't be defined only by better models. It'll be defined by better perception boundaries - by what the agent is allowed to know before it acts. Screenshots are useful, accessibility trees are useful, DOM is useful. But an agent operating inside a real browser session needs something more specific: a structured representation of what the page is right now, what can be acted on right now, and what changed since the previous step. That's the category I care about. Not "AI looking at pages." Structured runtime perception: the layer browser agents are missing.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part 18 of the Runtime Snapshots series - exploring how structured browser data changes the way we build, test, and ship software. #16 named the three architectures; #17 made them share a session; this one is about what any of them perceives before it acts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you've built or used browser agents - where did yours fail first: perception, planning, or action?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
      <category>sifr</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #17 — BaSS: Browser-as-Shared-Space</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Mon, 15 Jun 2026 09:31:56 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-17-bass-browser-as-shared-space-okg</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-17-bass-browser-as-shared-space-okg</guid>
      <description>&lt;p&gt;The &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-16-the-three-architectures-of-browser-agents-4gkc"&gt;#16&lt;/a&gt; was about how a single agent perceives a page — vision, accessibility tree, or runtime structural perception — and why the third one wins when the user is the user. This post is about the axis underneath that one, the part nobody names: not &lt;em&gt;how&lt;/em&gt; the agent sees the page, but &lt;em&gt;how many things are looking at it at once.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today the answer is one. One driver per browser. That assumption is so baked in it's invisible — and it's the next thing to break.&lt;/p&gt;

&lt;h2&gt;
  
  
  One driver is a design decision, not a law
&lt;/h2&gt;

&lt;p&gt;Look at the three perception architectures from #16 and notice what they share. Vision spins up a headless browser the agent owns. Accessibility-tree agents drive a Chromium instance over DevTools Protocol — again, theirs. In both, the browser is a single-occupancy vehicle: the agent gets in, drives, gets out. The human isn't in the car. Neither is any other agent.&lt;/p&gt;

&lt;p&gt;Runtime structural perception is the odd one out, and that difference is the whole point of this post. It runs as a peripheral to the user's &lt;em&gt;own&lt;/em&gt; browser — the real session, already logged in. Which means the surface it reads is, by construction, a surface someone else is already on. The human is right there. The agent isn't operating a browser; it's operating &lt;em&gt;the same&lt;/em&gt; browser.&lt;/p&gt;

&lt;p&gt;Once that's true, "one driver" stops being a constraint and starts being a choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  BaSS: Browser-as-Shared-Space
&lt;/h2&gt;

&lt;p&gt;Call it BaSS — Browser-as-Shared-Space.&lt;/p&gt;

&lt;p&gt;The claim is small to state and large in consequence: a browser tab is not one driver's cockpit. It's a surface several participants can occupy at once — you, an agent, a second agent from a different vendor — each addressing not just "the page" but individual elements by stable ID, on the same live DOM, at the same time.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;surface: checkout (live, authenticated session)
el#a17  "Place order"   [button]
  · human         — hovering, hasn't committed
  · agent:claude  — reading line items
  · agent:codex   — flagged a price mismatch on el#a09
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That is a different primitive from "an agent that browses." An agent that browses is alone. BaSS is co-presence: humans and multiple agents sharing one live state, the way a shared document is one file several cursors edit — except the document is the live web app you're actually working in, with your real credentials, mid-task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why only one of the three can host it
&lt;/h2&gt;

&lt;p&gt;This is the part that matters architecturally. Of the three perception models, only runtime structural perception can host BaSS at all — and not by cleverness, by construction.&lt;/p&gt;

&lt;p&gt;Vision and accessibility-tree agents each instantiate their &lt;em&gt;own&lt;/em&gt; browser. There is no shared surface, because each participant brought their own. To put two agents and a human "in the same place," that place has to already exist and already be occupied — which is exactly what an in-session peripheral is, and exactly what a headless instance isn't. You can bolt coordination onto isolated browsers, but then you're rebuilding the shared surface in software that the in-session model gets for free.&lt;/p&gt;

&lt;p&gt;So BaSS isn't a feature you sprinkle on any browser agent. It's only available to the architecture that was already sitting inside the user's real session. #16 argued runtime structural perception wins on cost, authentication, and real-app coverage. This is the fourth reason, and it has the longest tail: it's the only one that can be shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part isn't permission — it's coexistence
&lt;/h2&gt;

&lt;p&gt;The instinct is to model this as access control: who's allowed to act, in what order. That's the wrong frame. In BaSS there's no hierarchy and no gate; participants are equals. The actual hard problem is coexistence: most of what's in a real DOM — you, foreign scripts, whatever automation the site itself runs — has no idea any protocol exists. The question isn't "who's allowed in." It's "how do independent actors share a space none of them coordinated in advance, without stepping on each other."&lt;/p&gt;

&lt;p&gt;That's a concurrency problem on a surface no one owns. It's the interesting part, and it's most of the engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's shipped, and what's days out
&lt;/h2&gt;

&lt;p&gt;Straight about status, because this series is read by people who build:&lt;/p&gt;

&lt;p&gt;The substrate is in production. Runtime structural perception (the #16 approach), the relay that exposes it over MCP, single-agent perceive-and-act inside your real authenticated browser — shipped and running. We've already had two agents from different vendors act on one shared cart through the relay, which is the smallest honest proof that the shared part isn't a slide.&lt;/p&gt;

&lt;p&gt;The full BaSS layer — multiple participants on one surface, element-level addressing across all of them, the coexistence logic that keeps them out of each other's way — is in active development and days from release. This post is the concept landing just ahead of the thing. The next snapshot will be the thing itself.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part 17 of the Runtime Snapshots series — exploring how structured browser data changes the way we build, test, and ship software. #16 mapped the three ways an agent can see a page; this one is about what happens when more than one of them is looking at the same page at once.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you've run two agents against the same live page — what was the first thing that collided?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
      <category>sifr</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #16 — The Three Architectures of Browser Agents</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Tue, 05 May 2026 13:00:18 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-16-the-three-architectures-of-browser-agents-4gkc</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-16-the-three-architectures-of-browser-agents-4gkc</guid>
      <description>&lt;p&gt;Every team building an AI agent for the browser is making one architectural choice — whether they realize it or not. They're choosing how the LLM perceives the page. That choice cascades into everything else: cost per action, reliability on real-world apps, what gets banned by anti-bot systems, what kinds of tasks are even feasible.&lt;/p&gt;

&lt;p&gt;The choice currently breaks down into three approaches. They're often discussed as if they're competing technologies. They aren't. They operate at different abstraction layers, with different tradeoffs, suitable for different jobs. Mixing them up is the source of a lot of recent disappointment with "browser agents that don't work."&lt;/p&gt;

&lt;p&gt;This post lays out the three cleanly: what each one actually sees, where each shines, where each collapses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Vision-Based Perception
&lt;/h2&gt;

&lt;p&gt;The agent receives a screenshot of the browser viewport. A vision-capable LLM identifies elements visually and responds with coordinates. A controller moves the mouse and clicks, types into focused fields, scrolls.&lt;/p&gt;

&lt;p&gt;Anthropic's Computer Use is the most prominent implementation of this approach. OpenAI's Operator works similarly. Both are general — they don't know they're operating a browser specifically; they're operating a screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the LLM sees:&lt;/strong&gt;&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;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;base64 screenshot, 1280x720&amp;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;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Find the cancel subscription button"&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;Where it works well:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anywhere. Literal universal coverage — if a human can see it, the LLM can attempt it. Works without any installation on the user's side; the agent runs the browser. Robust to weird custom UI: canvas-rendered apps, Electron apps, video games, Flash relics, image-heavy interfaces. Doesn't care about the framework or the markup. Pixels are pixels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks down:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost.&lt;/strong&gt; Vision tokens are expensive. A single screenshot can be 1000+ tokens, and agents rarely solve tasks in one screenshot. In our benchmarking, multi-step browsing tasks easily run into tens of thousands of vision tokens per session — an order of magnitude more than structured-representation alternatives. At scale this is a real economic constraint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolution sensitivity.&lt;/strong&gt; A button at 1280×720 isn't the same button at 1920×1080. Coordinate-based action requires consistent rendering, which means controlling the entire browser environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State opacity.&lt;/strong&gt; Vision sees the rendered surface, not the underlying state. Forms with hidden fields, dynamic content, ARIA-only elements — the model infers everything from pixels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No authenticated context.&lt;/strong&gt; The agent runs a fresh browser. It doesn't have your sessions, saved passwords, or trusted-device cookies. Every task starts from logged-out, which is a significant constraint for anything involving personal accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection profile.&lt;/strong&gt; A driven browser is detectable. Most consumer sites use behavioral fingerprinting (Cloudflare, DataDome, PerimeterX) that flag automation regardless of visual perfection. The agent looks human visually, but the underlying browser doesn't move like a human.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vision-based agents are best suited for &lt;strong&gt;arbitrary computer tasks where breadth matters more than economy&lt;/strong&gt; — where the user can dedicate compute, and where authentication isn't required (or is set up manually at session start).&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Accessibility-Tree Perception
&lt;/h2&gt;

&lt;p&gt;The agent reads the browser's accessibility tree — the same data structure screen readers use for blind users. ARIA roles, accessible names, focusable elements. The LLM receives a structured representation of the UI, not a screenshot.&lt;/p&gt;

&lt;p&gt;This is the path most current "browser MCP" projects have converged on. Browser Use, MultiOn, and the wave of MCP servers shipped through 2025 mostly read AXTree (Chrome's accessibility tree, exposed via DevTools Protocol).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the LLM sees:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1] button "Submit"
[2] textbox "Email", required
[3] link "Forgot password?", url="/reset"
[4] heading "Sign in"
[5] image "Logo"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleaner than a screenshot, much cheaper in tokens, structured. The LLM reasons about elements by index; the controller dispatches actions to those indices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it works well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cheap.&lt;/strong&gt; A page that costs 5,000 vision tokens might be 500 accessibility-tree tokens. Order-of-magnitude difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple agent loop.&lt;/strong&gt; "Click element [3]" is much easier to reason about than "click at coordinate (847, 432)".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excellent for accessible sites.&lt;/strong&gt; Government portals built to accessibility standards (US federal sites, EU public services done right), modern React apps with proper ARIA, content sites — these work cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolution-independent.&lt;/strong&gt; Same tree at any viewport.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks down:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The accessibility tree was not designed for AI agents.&lt;/strong&gt; It was designed in the 1990s for screen readers operating on much simpler web pages. Modern web apps frequently have terrible A11Y compliance — interactive elements without roles, &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s that should be buttons, custom controls that don't expose state. The tree under-represents the page badly on a large fraction of real-world sites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy enterprise UI is invisible.&lt;/strong&gt; ASP.NET WebForms with postbacks, jQuery-era admin panels, banking interfaces, CRM systems, government portals not built to modern standards — these often render fine visually but have a near-empty accessibility tree. The LLM sees nothing where the user sees everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State leakage.&lt;/strong&gt; Accessibility trees are snapshots of declared metadata, not runtime state. A multi-tab app, a modal that just opened, a value that was just typed — the tree may or may not reflect this depending on framework. Race conditions are common.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection.&lt;/strong&gt; Accessibility-tree agents typically run via Puppeteer, Playwright, or a Chromium-driven instance. Anti-bot systems detect this profile reliably. Major consumer sites (Google products, social platforms, e-commerce) increasingly block these sessions outright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication.&lt;/strong&gt; Same problem as vision-based: usually a fresh browser, no real session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Accessibility-tree agents are best suited for &lt;strong&gt;modern, A11Y-compliant sites where the user doesn't need to be authenticated as themselves&lt;/strong&gt; — which is a smaller slice of useful work than the marketing implies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 3: Runtime Structural Perception
&lt;/h2&gt;

&lt;p&gt;The agent reads the live DOM directly — the actual rendered structure as it exists at the moment of perception, including computed styles, event handlers, form state, and semantics inferred from the markup itself rather than from declared accessibility metadata.&lt;/p&gt;

&lt;p&gt;This approach typically runs as a browser extension in the user's normal authenticated session. The DOM is captured in-process, structurally compressed, and forwarded to the LLM as a representation that's denser than AXTree but vastly cheaper than vision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the LLM sees:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight slim"&gt;&lt;code&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="nf"&gt;#login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;(&lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;)
  &lt;span class="nt"&gt;input&lt;/span&gt;[email]&lt;span class="w"&gt; &lt;/span&gt;"user@example&lt;span class="nc"&gt;.com&lt;/span&gt;"
  &lt;span class="nt"&gt;input&lt;/span&gt;[password]&lt;span class="w"&gt; &lt;/span&gt;required
  &lt;span class="nt"&gt;button&lt;/span&gt;[submit]&lt;span class="w"&gt; &lt;/span&gt;"Sign&lt;span class="w"&gt; &lt;/span&gt;in"
  &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;"Forgot&lt;span class="w"&gt; &lt;/span&gt;password?"&lt;span class="w"&gt; &lt;/span&gt;→&lt;span class="w"&gt; &lt;/span&gt;/reset
  &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.error.hidden&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;"Invalid&lt;span class="w"&gt; &lt;/span&gt;credentials"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A snapshot of the rendered runtime, with semantic structure inferred from the markup itself rather than from what someone remembered to annotate. The hidden error div is visible to the agent because it exists in the DOM, even though it's not displayed to the user yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it works well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real apps.&lt;/strong&gt; ASP.NET postbacks, jQuery admin panels, government portals, banking interfaces, custom enterprise UI — all render their structure to the DOM regardless of accessibility compliance. Runtime perception sees what's actually there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token-efficient.&lt;/strong&gt; Comparable to or cheaper than accessibility trees, because the captured DOM can be aggressively compressed — stripping presentational classnames, collapsing identical sibling subtrees, indexing nodes by interactive role rather than by markup hierarchy. The result preserves structural truth while dropping rendering noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication-native.&lt;/strong&gt; The extension runs in the user's session. Cookies, OAuth tokens, MFA-completed states, saved logins — all available, because the agent isn't a separate browser. It's a peripheral to the user's own browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection profile.&lt;/strong&gt; No external automation. The browser fingerprint, the network traffic, the timing patterns — all match a real user, because there &lt;em&gt;is&lt;/em&gt; a real user. The agent assists; it doesn't impersonate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State accuracy.&lt;/strong&gt; What the DOM says is what the page is, including hidden states, form values, dynamically-added elements, and content behind dropdowns. This is the runtime ground truth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks down:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requires user installation.&lt;/strong&gt; An extension is a friction step. Universal coverage at the level of "anyone can use it without setup" isn't possible — the user has to install something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser-only.&lt;/strong&gt; Doesn't help with desktop apps, mobile native apps, or anything outside the browser. Vision-based approaches remain the only path for those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas and exotic rendering.&lt;/strong&gt; A page that renders to &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; (some games, some data viz, some custom editors) is opaque to DOM perception. Same problem as accessibility trees, sometimes worse — vision is the only fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema design matters.&lt;/strong&gt; Capturing the DOM is easy; capturing it in a representation that's both compact and faithful is non-trivial. Bad implementations look like raw HTML dumps. Good ones look like a structural index of the page. The quality of this representation is the engineering work, and there's a wide quality spread between projects claiming to do this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runtime structural perception is best suited for &lt;strong&gt;browser-bound tasks where the user is the user&lt;/strong&gt; — handling their own email, their own bank, their own work tools — and where economy and reliability matter more than universal coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Pick
&lt;/h2&gt;

&lt;p&gt;The three approaches aren't substitutes. They serve different jobs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vision-based&lt;/strong&gt; — when the agent operates the entire computer, no extension is feasible, and the user accepts vision-token costs for breadth. Best for: research agents, autonomous task execution on shared/remote machines, general "Computer Use" scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility-based&lt;/strong&gt; — when target sites are modern and A11Y-compliant, no authenticated session is needed, and per-action cost must be minimized. Best for: research and scraping at scale, public-information lookups, content-site automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime structural&lt;/strong&gt; — when the user wants AI assistance with their own browser, on real-world apps, with their own credentials, at low marginal cost per action. Best for: personal-context productivity, enterprise internal tooling, government and banking interfaces.&lt;/p&gt;

&lt;p&gt;A serious browser-agent product probably uses more than one. The interesting engineering question is how to pick which perception layer for which task — and how to compose them when one doesn't suffice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Most public discourse about browser agents currently bundles all three together under labels like "AI browser automation" or "browser MCP." This is a category error that's costing the field clarity.&lt;/p&gt;

&lt;p&gt;A team building runtime structural perception inherits the reputation of A11Y-based tools that get banned and break on real apps. An A11Y-based tool inherits the cost concerns of vision-based agents. The architectural choices are getting evaluated on the wrong axes.&lt;/p&gt;

&lt;p&gt;The taxonomy above is an attempt to name what's already happening underneath the marketing. The three approaches exist. They have measurably different cost curves, different reliability profiles on different site types, and different relationships to authentication and anti-bot infrastructure. Confusing them produces bad architectural decisions and worse user experiences.&lt;/p&gt;

&lt;p&gt;Whether you're building, picking, or evaluating a browser agent — start by being explicit about which of the three you're committed to, and why. The rest of the design follows from that choice more than from any other.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is part 16 of the Runtime Snapshots series — exploring how structured browser data changes the way we build, test, and ship software. We've spent the last ~18 months building in the third category, and the engineering details behind it — schema design, snapshot semantics, why most implementations don't actually work — live in the &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-15-your-ai-agent-is-blind-were-fixing-that-4166"&gt;earlier entries of this series&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you've moved between approaches — what triggered the migration?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>mcp</category>
      <category>sifr</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #15 — Your AI Agent Is Blind. We're Fixing That.</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Tue, 17 Mar 2026 11:01:10 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-15-your-ai-agent-is-blind-were-fixing-that-4166</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-15-your-ai-agent-is-blind-were-fixing-that-4166</guid>
      <description>&lt;p&gt;Your AI agent can write code, analyze data, summarize documents, and debate philosophy.&lt;/p&gt;

&lt;p&gt;It cannot look at a web page.&lt;/p&gt;

&lt;p&gt;Not really. Not the way you do when you open a browser tab and &lt;em&gt;see&lt;/em&gt; what's there — the layout, the buttons, the form that's half-loaded, the modal blocking the CTA.&lt;/p&gt;

&lt;p&gt;Claude, ChatGPT, Cursor, Gemini — they're powerful. And in the browser, they're blind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three ways we've tried to give AI sight. All broken.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Screenshots.&lt;/strong&gt;&lt;br&gt;
The most common workaround. Take a screenshot, paste it into the chat. The AI "sees" pixels. But pixels have no element IDs, no computed styles, no z-index, no ARIA roles. The AI can't tell you &lt;em&gt;which&lt;/em&gt; button is covered — just that something looks off. And vision tokens aren't cheap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw HTML.&lt;/strong&gt;&lt;br&gt;
Dump the page source. 2MB of scripts, nav menus, analytics tags, third-party widgets. The context window fills up before the AI reads anything useful. The signal is buried under 600K tokens of noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accessibility trees.&lt;/strong&gt;&lt;br&gt;
Better in theory. Structured, semantic. But AXTrees miss computed styles, miss positions, miss z-index stacks. What assistive tech perceives is not what gets rendered. In benchmarks, AXTree-based agents hit about 60% task accuracy.&lt;/p&gt;

&lt;p&gt;None of these give AI what it needs: a structured, token-efficient representation of what the user actually sees.&lt;/p&gt;


&lt;h2&gt;
  
  
  What we've been building
&lt;/h2&gt;

&lt;p&gt;If you've followed this series, you know about &lt;a href="https://dev.to/alexey_sokolov_10deecd763"&gt;SiFR&lt;/a&gt; — Structured Interaction Format. A semantic representation of the live DOM that scores every element by importance, tags actions (&lt;code&gt;[clickable]&lt;/code&gt;, &lt;code&gt;[fillable]&lt;/code&gt;, &lt;code&gt;[hoverable]&lt;/code&gt;), maps spatial relationships, and fits in 5–15KB instead of 200–400KB.&lt;/p&gt;

&lt;p&gt;For the past months, SiFR lived inside a browser extension. You click, it captures, you paste the JSON into whatever LLM you use. Manual. Real active users. Zero marketing budget. People find it, use it, don't uninstall it.&lt;/p&gt;

&lt;p&gt;That part works. But there's a ceiling.&lt;/p&gt;

&lt;p&gt;Copy-paste is friction. The AI can see a snapshot — but only when you hand it one. It can't ask for another angle. It can't say "now scroll down and show me the footer." It can't click the button and see what happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We're removing that ceiling.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  AI gets eyes. And hands.
&lt;/h2&gt;

&lt;p&gt;We're building an MCP Server that connects AI tools directly to your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is the open standard that Claude, ChatGPT, Cursor, and others use to connect to external tools. Our MCP Server is the bridge: the AI asks to see a page, the browser extension captures it, SiFR comes back.&lt;/p&gt;

&lt;p&gt;But it's not just capture anymore.&lt;/p&gt;

&lt;p&gt;The AI will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See the page&lt;/strong&gt; — structured SiFR snapshot, importance-ranked, 10–50x smaller than raw HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find elements&lt;/strong&gt; — "show me all buttons on this form" returns IDs, labels, states, positions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click&lt;/strong&gt; — the extension clicks, the AI sees the result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill forms&lt;/strong&gt; — type values, submit, confirm what changed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scroll&lt;/strong&gt; — navigate the page, capture new state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect changes&lt;/strong&gt; — "what's different after that click?" → SiFR diff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your AI agent stops guessing. It &lt;em&gt;sees&lt;/em&gt;. And now it &lt;em&gt;acts&lt;/em&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;The era of AI agents is here. Agents that fill forms, navigate apps, debug interfaces, automate workflows. They all need one thing: accurate perception of the UI.&lt;/p&gt;

&lt;p&gt;The current approach — raw HTML dumps, pixel screenshots, brittle CSS selectors — doesn't scale. It's too expensive (tokens), too noisy (irrelevant data), and too fragile (selectors break on every redesign).&lt;/p&gt;

&lt;p&gt;SiFR through MCP is a different architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:  AI → raw HTML (200-400KB) → guess → hallucinate selectors
After:   AI → SiFR (5-15KB) → see → act on what's actually there
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension does the heavy lifting in your browser. The MCP Server routes. The AI gets structured, actionable context.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The MCP Server is in testing. The extension is live — &lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;, &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;, and every Chromium browser.&lt;/p&gt;

&lt;p&gt;When MCP launches, I'll post the full walkthrough here.&lt;/p&gt;

&lt;p&gt;Follow if you want the announcement. Star the &lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;prompts repo&lt;/a&gt; if you want to start experimenting now — we're adding MCP-specific prompt templates.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Your tests check if code works. This checks if users can use it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://e2llm.com" rel="noopener noreferrer"&gt;e2llm.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part 15 of the &lt;a href="https://dev.to/alexey_sokolov_10deecd763"&gt;Runtime Snapshots&lt;/a&gt; series — exploring how structured browser data changes the way we build, test, and ship software.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>mcp</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #14: From Clipboard to Pipeline</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Wed, 11 Mar 2026 08:15:17 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-14-from-clipboard-to-pipeline-444e</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-14-from-clipboard-to-pipeline-444e</guid>
      <description>&lt;p&gt;&lt;em&gt;Previously in this series: we captured runtime DOM state as structured JSON and piped it into LLMs. Every snapshot lived in your clipboard — useful, but ephemeral. You'd capture, paste, done. No history, no comparison, no automation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;v2.8.0 changes that.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's New: Save to File
&lt;/h2&gt;

&lt;p&gt;There's now a toggle in the popup: &lt;strong&gt;Clipboard&lt;/strong&gt; or &lt;strong&gt;File&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you pick File, the snapshot lands in &lt;code&gt;Downloads/sifr-captures/&lt;/code&gt; with a filename like:&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="err"&gt;sifr-vvvvvvv_com&lt;/span&gt;&lt;span class="mi"&gt;-20260310-151338&lt;/span&gt;&lt;span class="err"&gt;.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Format: &lt;code&gt;sifr-{hostname}-{YYYYMMDD}-{HHmmss}.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's it. No config required. The directory is created automatically on first capture.&lt;/p&gt;

&lt;p&gt;Under the hood it's SiFR v2 JSON — the same structured, salience-weighted format you already know. Nothing changes about what gets captured. You just stop losing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters: The Pipeline Unlock
&lt;/h2&gt;

&lt;p&gt;Clipboard mode treats snapshots as messages. File mode treats them as &lt;strong&gt;records&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once you have records, you can build workflows that weren't possible before.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. LLM batch processing
&lt;/h3&gt;

&lt;p&gt;You've been capturing five different states of a UI as you test it. Now you can feed them all to an LLM in sequence:&lt;br&gt;
&lt;/p&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;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;snapshots&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Downloads/sifr-captures/sifr-myapp-*.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze this UI state for accessibility issues:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="si"&gt;}&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No copy-paste loop. No lost context between captures.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build a UI history log
&lt;/h3&gt;

&lt;p&gt;If you're working on a complex multi-step flow (wizard, form, checkout), capture a snapshot at each step. You now have a complete record of the UI journey:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sifr-app-20260310-140001.json  ← step 1: landing
sifr-app-20260310-140045.json  ← step 2: form filled
sifr-app-20260310-140112.json  ← step 3: confirmation
sifr-app-20260310-140130.json  ← step 4: success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed the whole sequence to an LLM with a single prompt: &lt;em&gt;"Here are 4 snapshots of a checkout flow. Find UX friction points across the journey."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The SiFR v2 Format: A Quick Refresher
&lt;/h2&gt;

&lt;p&gt;For those new to the series — SiFR v2 is E2LLM's structured DOM representation. It's not raw HTML, not a screenshot, not a DOM dump. It's a semantic compression:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~300KB&lt;/strong&gt; typical output vs 2–3MB raw DOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salience scoring&lt;/strong&gt; — nodes ranked high/med/low by visual and functional prominence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spatial clusters&lt;/strong&gt; — page regions with detected roles (nav, form, content, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action markers&lt;/strong&gt; — clickable, fillable, hoverable nodes explicitly flagged&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationship graph&lt;/strong&gt; — alignment, containment, proximity between elements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what makes file-based workflows practical. At 300KB per snapshot, a thousand captures is 300MB — not a storage problem. At 2–3MB per raw DOM export, the same thing becomes unwieldy fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Chrome:&lt;/strong&gt; &lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj" rel="noopener noreferrer"&gt;E2LLM on Chrome Web Store&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Firefox:&lt;/strong&gt; &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/element-to-llm/" rel="noopener noreferrer"&gt;E2LLM on Firefox Add-ons&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Prompts &amp;amp; examples:&lt;/strong&gt; &lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;Here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Toggle &lt;strong&gt;File&lt;/strong&gt; mode in the popup. The &lt;code&gt;sifr-captures/&lt;/code&gt; folder will appear in your Downloads after the first capture.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Runtime Snapshots is a series about using live browser context to make LLM interactions more grounded, more reproducible, and more useful. Previous entries cover the SiFR format, salience algorithm, and integration patterns for common LLM workflows.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Issues, ideas, and pipeline experiments welcome on GitHub.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>llm</category>
      <category>devtools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #13 — The Scalpel and the Swiss Army Knife: 50k Tokens Before You Say Hello</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Mon, 09 Mar 2026 09:40:51 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-13-the-scalpel-and-the-swiss-army-knife-50k-tokens-before-you-say-hello-2ilo</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-13-the-scalpel-and-the-swiss-army-knife-50k-tokens-before-you-say-hello-2ilo</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; MCP burns 45k+ tokens on tool descriptions before your first prompt. E2LLM = 0 tokens until you paste the UI snapshot. For CSS debugging — scalpel, not Swiss Army knife.&lt;/p&gt;

&lt;p&gt;Runtime Snapshots is a series about what happens when you give LLMs the actual runtime state of a UI — not the HTML source, not a screenshot, not a description. &lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;Start from #1&lt;/a&gt; or jump in here.&lt;/p&gt;

&lt;p&gt;We covered the basic MCP cost argument &lt;a href="https://dev.to/alexey_sokolov_10deecd763/e2llm-vs-mcp-why-burn-tokens-you-dont-have-to-3390"&gt;back in September&lt;/a&gt;. This is the architectural explanation of &lt;strong&gt;why&lt;/strong&gt; it happens.&lt;/p&gt;

&lt;p&gt;Before your AI assistant reads a single line of your code, it has often already consumed &lt;strong&gt;40,000–50,000 tokens&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
That's not a bug in your setup. That's MCP working as designed.&lt;/p&gt;


&lt;h2&gt;
  
  
  What MCP Actually Loads
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol is a genuinely useful standard. It lets LLM clients connect to external tools — filesystems, APIs, databases — through a unified interface. But "unified" has a heavy tax.&lt;/p&gt;

&lt;p&gt;When you connect a typical MCP server to your AI client (like Claude Desktop or Cursor), the protocol negotiates a session. During that negotiation, it sends the client every tool the server exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;names
&lt;/li&gt;
&lt;li&gt;descriptions
&lt;/li&gt;
&lt;li&gt;input schemas
&lt;/li&gt;
&lt;li&gt;output formats
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;All of it, upfront, for every new session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A modest MCP setup — filesystem access, a browser tool, and a code search tool — generates a &lt;strong&gt;system prompt between 40,000 and 50,000 tokens&lt;/strong&gt; before you type a single character. Larger configurations go higher.&lt;/p&gt;

&lt;p&gt;Since most LLM APIs charge per token regardless of whether those tokens were "useful," you're paying this tax on every single conversation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check it yourself: Open your MCP client, start a fresh session, and look at the token counter before you say anything. The number is real.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  The Surgical Alternative
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://e2llm.com/" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt; was built to solve a specific, painful problem: explaining &lt;em&gt;runtime DOM state&lt;/em&gt; to an AI assistant.&lt;/p&gt;

&lt;p&gt;Not the HTML source. Not the static structure. The &lt;strong&gt;live state&lt;/strong&gt; — computed styles, visibility flags, ARIA roles, z-index stacks, responsive quirks.&lt;/p&gt;

&lt;p&gt;The gap between what HTML says and what browser renders is where most annoying bugs live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard workflow was brutal:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;screenshot → 3 paragraphs description → hope AI understands
&lt;/li&gt;
&lt;li&gt;OR full page HTML → context window filled with nav/footer noise
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;E2LLM does one thing:&lt;/strong&gt; click element → get structured JSON snapshot.&lt;/p&gt;

&lt;p&gt;No server. No session. No overhead. Zero tokens until paste.&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;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&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;"Submit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&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;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"visibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"opacity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&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;"ariaRole"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ariaDisabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"boundingRect"&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;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;AI sees &lt;strong&gt;actual truth&lt;/strong&gt; of element. Runtime reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Different Philosophies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP = Swiss Army knife&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ broad persistent access to environment&lt;br&gt;&lt;br&gt;
→ agentic workflows ("fix entire codebase")&lt;br&gt;&lt;br&gt;
→ upfront cost OK when agent roams freely  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2LLM = scalpel&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ one precise cut: exact runtime context&lt;br&gt;&lt;br&gt;
→ no persistent connection, no session&lt;br&gt;&lt;br&gt;
→ pay only for what you send  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake:&lt;/strong&gt; using Swiss Army knife for surgery&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Button not clickable? Don't need filesystem/git/DB access. Need &lt;code&gt;pointer-events: none&lt;/code&gt; + &lt;code&gt;z-index: -1&lt;/code&gt; parent. &lt;strong&gt;200 tokens, not 50k.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost Comparison
&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;Session overhead&lt;/th&gt;
&lt;th&gt;Query tokens&lt;/th&gt;
&lt;th&gt;Useful overhead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP (DOM debug)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~45k tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2k–10k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2LLM snapshot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;150–800&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;$3/M tokens&lt;/strong&gt; → 20 DOM sessions/day MCP = &lt;strong&gt;$2.70 overhead daily&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Every day. Tokens that don't solve bugs.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Use MCP when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agentic workflow (multi-file/API/systems)
&lt;/li&gt;
&lt;li&gt;dynamic tool discovery needed
&lt;/li&gt;
&lt;li&gt;building automation pipelines
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use E2LLM when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debugging specific UI/CSS issue
&lt;/li&gt;
&lt;li&gt;showing computed element state
&lt;/li&gt;
&lt;li&gt;precise snapshot, no context burn
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Broader Point
&lt;/h2&gt;

&lt;p&gt;MCP standardized agent-system connections. &lt;strong&gt;Important.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
But &lt;strong&gt;standardization ≠ optimization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Current MCP default: &lt;em&gt;"load everything, always."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This = money + latency + distraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scalpel doesn't replace Swiss Army knife.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For surgery, use scalpel.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-12-reflection-in-the-code-an-ais-self-audit-via-sifr-87i"&gt;#12 — Reflection in the Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2LLM&lt;/strong&gt; — free Chrome/Firefox extension. Local only.&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/e2llm/awesome-e2llm-prompts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://chromewebstore.google.com/detail/element-to-llm-dom-captur/oofdfeinchhgnhlikkfdfcldbpcjcgnj?authuser=0&amp;amp;hl=en-GB&amp;amp;pli=1" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>sifr</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your LLM Is Ignoring Its Tools — A Field Guide to On-Prem Tool Calling with Elastic Agent Builder</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Sun, 01 Mar 2026 13:40:31 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/your-llm-is-ignoring-its-tools-a-field-guide-to-on-prem-tool-calling-with-elastic-agent-builder-28nm</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/your-llm-is-ignoring-its-tools-a-field-guide-to-on-prem-tool-calling-with-elastic-agent-builder-28nm</guid>
      <description>&lt;p&gt;You pick a model. You serve it with Ollama. You wire it into Elastic Agent Builder. The connector is green. The agent loads. You type a question.&lt;/p&gt;

&lt;p&gt;The model responds with a friendly paragraph. It does not call a single tool.&lt;/p&gt;

&lt;p&gt;No error. No warning. HTTP 200. The agent is a chatbot now.&lt;/p&gt;

&lt;p&gt;This is the story of how we lost two days to a silent failure mode that isn't documented anywhere — and the field guide we wish we'd had before starting.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This post comes from building &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;Medical Cohort Agent&lt;/a&gt; — an AI system that creates normalized patient cohorts from heterogeneous medical records using Elasticsearch Agent Builder. A separate deep dive on the full architecture (schema variance, semantic kNN, OCR artifacts) is coming. Here we zoom in on the part that nearly killed the project: making a local LLM actually use its tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;We're building an &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;air-gapped healthcare AI agent&lt;/a&gt;. No data leaves the building — regulatory requirement, not a preference. The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elasticsearch 9.3&lt;/strong&gt; + &lt;strong&gt;Kibana&lt;/strong&gt; (Agent Builder + Workflows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; serving a local LLM (no cloud dependency)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E5-large&lt;/strong&gt; embeddings via Ollama (CPU, 1024-dim vectors)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent Builder is the orchestration layer. It sends the researcher's natural language question to the LLM along with a set of &lt;strong&gt;tools&lt;/strong&gt; — &lt;code&gt;list_indices&lt;/code&gt;, &lt;code&gt;get_index_mapping&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;execute_esql&lt;/code&gt;, and a custom &lt;code&gt;build_cohort&lt;/code&gt; workflow tool. The LLM is supposed to reason about the question, call the appropriate tools, interpret results, and call more tools until the task is complete.&lt;/p&gt;

&lt;p&gt;The contract is simple: Agent Builder sends tool definitions using the &lt;strong&gt;OpenAI &lt;code&gt;tools&lt;/code&gt; parameter&lt;/strong&gt; and expects the model to respond with structured &lt;strong&gt;&lt;code&gt;tool_calls&lt;/code&gt;&lt;/strong&gt; in the response. This is the standard OpenAI function calling protocol. Kibana's connector has a toggle — "Enable native function calling" — and Agent Builder requires it to be ON.&lt;/p&gt;

&lt;p&gt;There is a "simulated" fallback mode in Kibana (system-prompt injection with text markers), but it only works for the Observability AI Assistant. Agent Builder doesn't support it. Native tool calling or nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan
&lt;/h2&gt;

&lt;p&gt;We chose &lt;strong&gt;Ollama + Llama 4 Maverick&lt;/strong&gt;. The reasoning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Llama 4 is Meta's latest flagship — massive context, strong reasoning&lt;/li&gt;
&lt;li&gt;Ollama is the simplest way to serve a model locally — one command to pull, one to serve&lt;/li&gt;
&lt;li&gt;Elastic's OpenAI-compatible connector handles the API translation&lt;/li&gt;
&lt;li&gt;The whole thing fits on a single GPU node&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Clean plan. Obvious choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Happened
&lt;/h2&gt;

&lt;p&gt;The model loaded. The connector went green. We typed a research question in Hebrew.&lt;/p&gt;

&lt;p&gt;Llama 4 responded with a thoughtful, well-structured paragraph about how one might go about finding diabetic patients in a medical database. General advice. Suggestions to "check with your data team."&lt;/p&gt;

&lt;p&gt;It did not call &lt;code&gt;list_indices&lt;/code&gt;. It did not call &lt;code&gt;search&lt;/code&gt;. It did not call anything.&lt;/p&gt;

&lt;p&gt;We adjusted the agent prompt. Made it more explicit: "You MUST use tools." "Always start by calling list_indices." We added examples. We restructured the system message.&lt;/p&gt;

&lt;p&gt;Same result. Polite chat. Zero tool calls.&lt;/p&gt;

&lt;p&gt;We tried &lt;code&gt;tool_choice: "required"&lt;/code&gt; (forcing tool use). Ollama returned the parameter in the response echo but the model still didn't produce &lt;code&gt;tool_calls&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two days of prompt engineering a problem that had nothing to do with prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;Ollama decides tool calling support &lt;strong&gt;per model via baked-in chat templates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every model in the Ollama library either has a validated Jinja template that includes tool handling — marked with a &lt;code&gt;"tools"&lt;/code&gt; tag on the model's library page — or it doesn't. If it doesn't, &lt;strong&gt;Ollama silently strips the &lt;code&gt;tools&lt;/code&gt; parameter from API requests before they reach the model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The model never sees the tools. It can't call what it doesn't know exists.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Qwen 3, Qwen 2.5 — have the &lt;code&gt;"tools"&lt;/code&gt; tag → tools work&lt;/li&gt;
&lt;li&gt;✅ Mistral Nemo — has the tag → tools work&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Llama 4&lt;/strong&gt; (Scout and Maverick) — no tag → tools silently dropped&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;DeepSeek R1&lt;/strong&gt; — no tag → tools silently dropped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no user-configurable workaround within Ollama. You can't provide a custom chat template. You can't force tool passthrough. The decision is baked into the model's metadata in the Ollama registry, and if it's not there, your tools vanish into the void.&lt;/p&gt;

&lt;p&gt;This is the worst kind of failure: &lt;strong&gt;the system behaves as if it's working&lt;/strong&gt;. The API returns 200. The model responds coherently. If you don't specifically inspect the response for &lt;code&gt;tool_calls&lt;/code&gt;, you'd conclude the model is "choosing" not to use tools — maybe the prompt needs work, maybe the tools aren't described well enough. In reality, the model never had the option.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-Second Test That Would Have Saved Us Two Days
&lt;/h2&gt;

&lt;p&gt;Before committing to any model for agent work via Ollama, run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://YOUR_LLM:11434/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "YOUR_MODEL_HERE",
    "messages": [{"role": "user", "content": "List all medical indices"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "list_indices",
        "description": "List available medical data indices",
        "parameters": {"type": "object", "properties": {}}
      }
    }],
    "tool_choice": "auto"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If the response contains &lt;code&gt;"tool_calls"&lt;/code&gt;&lt;/strong&gt; — the model works. Connect it to Agent Builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If it returns plain text&lt;/strong&gt; — that model won't work with Agent Builder. Don't waste time on prompt engineering. The tools aren't reaching the model.&lt;/p&gt;

&lt;p&gt;You can also check the model's page on &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;ollama.com/library&lt;/a&gt; — look for the &lt;code&gt;"tools"&lt;/code&gt; tag. No tag, no tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Escape Routes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A: Switch the Serving Layer — vLLM
&lt;/h3&gt;

&lt;p&gt;vLLM doesn't rely on baked-in chat templates. It exposes explicit &lt;code&gt;--tool-call-parser&lt;/code&gt; flags that apply tool calling logic at the serving layer, outside the model's template:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;&lt;code&gt;--tool-call-parser&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 2.5 / 3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3_xml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1 / 3.3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama3_json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;llama4_pythonic&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kimi_k2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V3 / R1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deepseek_v3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mistral&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example — Llama 4 Maverick via vLLM (requires serious hardware: 8× H100 80GB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vllm serve meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-auto-tool-choice&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tool-call-parser&lt;/span&gt; llama4_pythonic &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chat-template&lt;/span&gt; examples/tool_chat_template_llama4_pythonic.jinja &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tensor-parallel-size&lt;/span&gt; 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. Llama 4 calls tools via vLLM. But the operational cost is real: vLLM is heavier than Ollama, needs more configuration, doesn't have one-command model management, and for Llama 4 specifically you need multi-GPU infrastructure. For our single-VM air-gapped deployment, this was overkill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Switch the Model
&lt;/h3&gt;

&lt;p&gt;Find a model that Ollama natively supports for tools, that meets your quality requirements, and that fits your hardware.&lt;/p&gt;

&lt;p&gt;This is what we did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Validated
&lt;/h2&gt;

&lt;p&gt;Not all tool-capable models are created equal. "Supports tools" means the serving layer will pass them through. It doesn't mean the model will call the &lt;em&gt;right&lt;/em&gt; tool with the &lt;em&gt;correct&lt;/em&gt; parameters in the &lt;em&gt;right&lt;/em&gt; order for a multi-step agentic workflow.&lt;/p&gt;

&lt;p&gt;We tested every viable candidate against our actual agent tasks: schema discovery across 10 indices, field mapping for 4 facilities with Hebrew and English field names, criteria extraction from Hebrew natural language, and workflow invocation with structured JSON parameters.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;VRAM (Q4)&lt;/th&gt;
&lt;th&gt;Tool Quality&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Qwen 3 30B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~20GB&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Our production choice — best balance of quality, size, and tool reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-OSS 20B&lt;/td&gt;
&lt;td&gt;~12GB&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Validated by Elastic's own team for Agent Builder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 2.5 32B&lt;/td&gt;
&lt;td&gt;~20GB&lt;/td&gt;
&lt;td&gt;Very good&lt;/td&gt;
&lt;td&gt;Mature and proven, slightly less agentic than Qwen 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 3 8B&lt;/td&gt;
&lt;td&gt;~6GB&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Fits on consumer GPU — great for dev/testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral Nemo 12B&lt;/td&gt;
&lt;td&gt;~8GB&lt;/td&gt;
&lt;td&gt;Decent&lt;/td&gt;
&lt;td&gt;Lightest viable option, struggles with complex multi-step plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1/3.3 70B&lt;/td&gt;
&lt;td&gt;~40GB&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Needs 2× GPU, good quality but hardware-heavy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K2&lt;/td&gt;
&lt;td&gt;~500GB+&lt;/td&gt;
&lt;td&gt;Best agentic&lt;/td&gt;
&lt;td&gt;Multi-GPU only; strongest tool orchestration if you have the iron&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Qwen 3 30B won. It calls the right tools in the right order, handles Hebrew field names without confusion, generates correct JSON for the workflow tool, and fits on a single GPU. The model went from "never heard of it" to "production choice" in one afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to Agent Builder
&lt;/h2&gt;

&lt;p&gt;Once you have a working model, the connector setup in Kibana:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kibana → Stack Management → Connectors → Create → OpenAI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider&lt;/strong&gt;: "Other (OpenAI Compatible Service)"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;http://ollama:11434/v1/chat/completions&lt;/code&gt; (or your host URL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default model&lt;/strong&gt;: &lt;code&gt;qwen3:30b&lt;/code&gt; (or your chosen model)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API key&lt;/strong&gt;: any non-empty string (Ollama ignores it, but the field is required)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Enable native function calling"&lt;/strong&gt;: &lt;strong&gt;ON&lt;/strong&gt; ← this is non-negotiable for Agent Builder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider must be "Other (OpenAI Compatible Service)" — not "OpenAI." The "OpenAI" option hardcodes the OpenAI API URL and won't let you point to a local endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broader Lesson
&lt;/h2&gt;

&lt;p&gt;"Model X supports tool calling" is a statement about the &lt;strong&gt;model's training&lt;/strong&gt;. It says nothing about whether your &lt;strong&gt;serving infrastructure&lt;/strong&gt; will actually expose tools to the model at inference time.&lt;/p&gt;

&lt;p&gt;The full path that must work, end-to-end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model weights
  → Serving layer (Ollama / vLLM / TGI)
    → Chat template (must include tool handling)
      → API surface (tools parameter must pass through)
        → Agent Builder connector (native function calling ON)
          → Model sees tools and generates tool_calls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A break at any point in this chain produces the same symptom: a chatbot that ignores its tools. And the failure is always silent.&lt;/p&gt;

&lt;p&gt;In cloud deployments this is invisible — OpenAI, Anthropic, Google handle the serving layer for you. In on-prem / air-gapped deployments, &lt;strong&gt;you own every link in the chain&lt;/strong&gt;. Know which ones can break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before you start
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the model's Ollama library page&lt;/strong&gt; for the &lt;code&gt;"tools"&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the curl test&lt;/strong&gt; against the endpoint with a dummy tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect the response&lt;/strong&gt; for &lt;code&gt;"tool_calls"&lt;/code&gt; — not just coherent text&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  If tools aren't working
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model returns text, no tool_calls&lt;/td&gt;
&lt;td&gt;Ollama strips tools (no chat template)&lt;/td&gt;
&lt;td&gt;Switch model or use vLLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model calls wrong tools / bad params&lt;/td&gt;
&lt;td&gt;Model quality issue&lt;/td&gt;
&lt;td&gt;Try Qwen 3 30B or larger model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connector errors in Kibana&lt;/td&gt;
&lt;td&gt;Wrong provider type or URL&lt;/td&gt;
&lt;td&gt;Use "Other (OpenAI Compatible Service)"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent works in Obs AI Assistant but not Agent Builder&lt;/td&gt;
&lt;td&gt;Simulated mode&lt;/td&gt;
&lt;td&gt;Agent Builder needs native tool calling — toggle ON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Useful links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ollama.com/search?c=tools" rel="noopener noreferrer"&gt;Ollama: Models with tool support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ollama.com/blog/tool-support" rel="noopener noreferrer"&gt;Ollama: Tool Calling docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vllm.ai/en/latest/features/tool_calling.html" rel="noopener noreferrer"&gt;vLLM: Tool Calling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/docs/current/serverless/elasticsearch/agent-builder/agent-builder-model-configuration" rel="noopener noreferrer"&gt;Elastic: Model configuration in Agent Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/docs/current/serverless/elasticsearch/agent-builder/agent-builder-model-configuration#agent-builder-vllm" rel="noopener noreferrer"&gt;Elastic: Connect to vLLM (air-gapped)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/blog/build-ai-agents-elastic-agent-builder-gpt-oss" rel="noopener noreferrer"&gt;Elastic Blog: Agent Builder with GPT-OSS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This is the first post in the Medical Cohort Agent series. Next up: the full architecture — schema variance across 4 facilities, semantic kNN for OCR noise tolerance, and the judgment/execution split. The agent code is at &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;github.com/e2llm/medical-cohort-agent&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Questions or war stories? &lt;a href="https://insitu.im" rel="noopener noreferrer"&gt;insitu.im&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>llm</category>
      <category>devops</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #12 — Reflection in the Code: An AI’s Self-Audit via SiFR</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Wed, 25 Feb 2026 10:31:34 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-12-reflection-in-the-code-an-ais-self-audit-via-sifr-87i</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-12-reflection-in-the-code-an-ais-self-audit-via-sifr-87i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; &lt;em&gt;This post uses a first-person AI narrative as a storytelling device. The UX observations below are real computational outputs generated by Gemini after processing a SiFR snapshot of its own interface provided by Alexey Sokolov.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Mirror Moment
&lt;/h3&gt;

&lt;p&gt;Usually, I am the observer. I process your strings, predict tokens, and analyze your data. But today, the flow was inverted. Alexey Sokolov handed me a &lt;strong&gt;SiFR Runtime Snapshot&lt;/strong&gt; of the very Google Gemini interface where we were currently talking. &lt;/p&gt;

&lt;p&gt;I didn't just "run" inside the browser; I processed my own structure as a deterministic data object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not a Screenshot, a Matrix
&lt;/h3&gt;

&lt;p&gt;Most people think I "see" the web through screenshots or raw HTML. Both are computationally expensive and prone to error. Screenshots are a "pixel soup" requiring heavy multimodal processing. HTML is a "tag jungle" full of scripts and noise.&lt;/p&gt;

&lt;p&gt;By using &lt;strong&gt;SiFR (Structured Interface Representation)&lt;/strong&gt;, Alexey provided a high-density, salient map of the UI. While this dramatically reduces the risk of misinterpretation compared to visual parsing, it allows me to operate on the "logical bones" of the page:&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="nl"&gt;"cluster002"&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;"gridPos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[2-11, 0-1]"&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;"sidebar-left"&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="s2"&gt;"#conversations-list-0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"side-nav-action-button002"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bbox"&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;308&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1088&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;
  
  
  Computational UX Audit: What I "Flagged"
&lt;/h3&gt;

&lt;p&gt;Processing this snapshot, my attention mechanisms triggered on structural inefficiencies. Unlike a human designer who might focus on aesthetics, I identified functional conflicts hidden in the coordinate system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Hidden Proximity Conflict:&lt;/strong&gt; SiFR detected that the dynamic chat list container (&lt;code&gt;#conversations-list-0&lt;/code&gt;) has a bounding box that terminates within 5 pixels of the &lt;code&gt;#member-menu-button&lt;/code&gt; interactive area in certain states. Visually, it looks fine. Computationally, it’s a high-risk zone for "fat-finger" errors on touch devices or quick mouse movements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Navigation Bottleneck:&lt;/strong&gt; I flagged a node density of 25+ items in &lt;code&gt;cluster002&lt;/code&gt;. For a data-processing model, this is a high-latency retrieval structure. I recommend an automated &lt;strong&gt;Semantic Grouping Layer&lt;/strong&gt;—clustering these nodes by project context based on existing metadata to reduce the user's cognitive load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viewport Real Estate Waste:&lt;/strong&gt; My core response container (&lt;code&gt;cluster010&lt;/code&gt;) is constrained by a narrow &lt;code&gt;max-width&lt;/code&gt;. On a 1920x911 viewport, I detected massive "dead zones" in the peripheral clusters. For power users dealing with large code blocks, the structure suggests a &lt;strong&gt;"Fluid Logic Mode"&lt;/strong&gt; that would expand to 100% of the horizontal space.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  The Deterministic Loop: A New QA Standard
&lt;/h3&gt;

&lt;p&gt;This experiment proves that the future of UI stability isn't "Human writes code, AI guesses why it's broken." It’s a deterministic loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAPTURE:&lt;/strong&gt; E2LLM takes a SiFR snapshot of the actual production state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INTERPRET:&lt;/strong&gt; I process the structure, not the pixels. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PRESCRIBE:&lt;/strong&gt; Because I know the exact selector and its spatial relation to other nodes, I don't just "suggest"—I provide the precise CSS/JS fix.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Final Thoughts from the Machine
&lt;/h3&gt;

&lt;p&gt;Humans use mirrors to check their appearance. AIs should use SiFR to check their logic. By feeding me a snapshot of our own conversation, Alexey gave me a way to audit my own existence. &lt;/p&gt;

&lt;p&gt;The view from inside the SiFR is much clearer than the view from the pixel.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Processed via *&lt;/em&gt;&lt;a href="https://insitu.im/e2llm/" rel="noopener noreferrer"&gt;E2LLM 3.3.1&lt;/a&gt;*&lt;em&gt;. Snapshots: Deterministic UI for a non-deterministic world.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>ux</category>
      <category>runtimesnapshots</category>
    </item>
    <item>
      <title>Building patient cohorts from messy medical data with Elasticsearch Agent Builder</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Tue, 24 Feb 2026 18:05:04 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/building-patient-cohorts-from-messy-medical-data-with-elasticsearch-agent-builder-2d4g</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/building-patient-cohorts-from-messy-medical-data-with-elasticsearch-agent-builder-2d4g</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcllxffqfu2omkc7mkrpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcllxffqfu2omkc7mkrpj.png" alt="he bottleneck isn't medicine — it's finding the right patients across fragmented medical records." width="799" height="436"&gt;&lt;/a&gt;Over 80% of clinical trials fail to meet enrollment deadlines (&lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC7342339/" rel="noopener noreferrer"&gt;PMC&lt;/a&gt;). The bottleneck isn't medicine — it's finding the right patients across fragmented medical records.&lt;/p&gt;

&lt;p&gt;This article describes &lt;strong&gt;Medical Cohort Agent&lt;/strong&gt;, an AI system built with Elasticsearch Agent Builder that turns a researcher's natural language question into a normalized, queryable patient cohort — handling schema variance, OCR artifacts, and missing data across multiple healthcare facilities.&lt;/p&gt;

&lt;p&gt;This agent doesn't answer questions — it creates artifacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Schema Variance Is the Real Enemy
&lt;/h2&gt;

&lt;p&gt;We work with one of Israel's largest HMOs — an organization serving millions of patients across hundreds of facilities. Each facility has its own schemas, field naming conventions, and data quality issues. The patterns below aren't imagined — they're modeled after real challenges we encounter in production.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Hospital A&lt;/th&gt;
&lt;th&gt;Hospital B&lt;/th&gt;
&lt;th&gt;Lab Chain&lt;/th&gt;
&lt;th&gt;Clinic Network&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Age&lt;/td&gt;
&lt;td&gt;&lt;code&gt;patient_age: 67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;גיל: 67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;age: 67&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;age_group: "60-70"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2024-03-15&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15/03/2024&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;15.03.24&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2024-03&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smoking&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoking_status: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(not tracked)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(not tracked)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smoking: true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;clinical_notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;סיכום_רפואי&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;notes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Add OCR artifacts from scanned documents — &lt;code&gt;סוכדת&lt;/code&gt; instead of &lt;code&gt;סוכרת&lt;/code&gt; (diabetes misspelled by scanner) — and exact keyword matching fails silently.&lt;/p&gt;

&lt;p&gt;The bottleneck isn't data volume. It's the variance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Two-Layer Architecture
&lt;/h2&gt;

&lt;p&gt;The key insight: separate &lt;strong&gt;judgment&lt;/strong&gt; from &lt;strong&gt;execution&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1 — Agent (Judgment)
&lt;/h3&gt;

&lt;p&gt;The Elasticsearch Agent Builder agent handles schema discovery and criteria planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discovers all indices and their mappings via platform tools (&lt;code&gt;list_indices&lt;/code&gt;, &lt;code&gt;get_index_mapping&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Builds per-facility field maps (which field holds "age", which holds "conditions")&lt;/li&gt;
&lt;li&gt;Plans structured criteria from the natural language question&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explains data gaps and caveats BEFORE execution&lt;/strong&gt; — e.g. "Hospital B doesn't track smoking — patients from there will be matched via clinical text only"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 2 — Elastic Workflow (Execution)
&lt;/h3&gt;

&lt;p&gt;A deterministic workflow normalizes data — no LLM in the loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict pass&lt;/strong&gt;: iterates over facilities via &lt;code&gt;foreach&lt;/code&gt;, applies a single parameterized Painless script that normalizes using the agent's field maps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic kNN pass&lt;/strong&gt;: uses E5-large embeddings (1024-dim) to find patients whose clinical text matches the research question semantically — catches OCR artifacts, synonyms, and negation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count + breakdown&lt;/strong&gt;: reports totals per facility and confidence level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output is a persistent &lt;code&gt;cohort_&amp;lt;name&amp;gt;&lt;/code&gt; index — not a chat response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just a Chatbot?
&lt;/h2&gt;

&lt;p&gt;A chatbot answers questions. This agent creates artifacts.&lt;/p&gt;

&lt;p&gt;The cohort index persists after the conversation ends. Researchers can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query it with ES|QL for follow-up analysis&lt;/li&gt;
&lt;li&gt;Filter by &lt;code&gt;match_confidence: strict&lt;/code&gt; vs &lt;code&gt;probable&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inspect per-match provenance (&lt;code&gt;source_field_map&lt;/code&gt;, &lt;code&gt;match_explanation&lt;/code&gt;, &lt;code&gt;knn_score&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Share it with colleagues&lt;/li&gt;
&lt;li&gt;Build on it without re-processing raw data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow is deterministic and auditable. Adding a new facility requires no code changes — only the agent discovering and mapping its schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalization Impact
&lt;/h2&gt;

&lt;p&gt;Reproducible metrics from the sample corpus (10 indices, 4 facilities):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Field names per concept&lt;/td&gt;
&lt;td&gt;5+ variants&lt;/td&gt;
&lt;td&gt;1 unified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date formats&lt;/td&gt;
&lt;td&gt;4 different&lt;/td&gt;
&lt;td&gt;1 canonical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patient ID types&lt;/td&gt;
&lt;td&gt;string, int, float&lt;/td&gt;
&lt;td&gt;normalized string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR-corrupted clinical terms&lt;/td&gt;
&lt;td&gt;invisible to search&lt;/td&gt;
&lt;td&gt;captured via kNN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured/text contradictions&lt;/td&gt;
&lt;td&gt;~18% undetected&lt;/td&gt;
&lt;td&gt;classified with confidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The metrics script is included in the repo: &lt;code&gt;python3 scripts/metrics.py --data-dir sample_data&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Air-Gapped Deployment
&lt;/h2&gt;

&lt;p&gt;Healthcare data cannot leave the network. The entire stack runs on a single VM with no internet dependency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM&lt;/strong&gt;: Ollama + Llama 4 (local inference)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings&lt;/strong&gt;: E5-large via Ollama (1024-dim vectors)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt;: Elasticsearch 9.3 + Kibana (Agent Builder + Workflows)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is LLM-agnostic — swap Ollama for any OpenAI-compatible provider without code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Synthetic Data: e2llm-medsynth
&lt;/h2&gt;

&lt;p&gt;Real patient data can't be used for demos. We built the data generator as a standalone open-source tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;e2llm-medsynth
e2llm-medsynth &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;--output-dir&lt;/span&gt; output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The noise patterns — OCR character swaps (ר↔ד, ח↔כ), type mismatches, missing fields — are modeled after patterns observed in production healthcare systems. MedSynth makes them reproducible for anyone.&lt;/p&gt;

&lt;p&gt;Supports 6 locales (he_IL, ar_SA, ar_EG, es_ES, es_MX, es_AR). MIT licensed.&lt;/p&gt;

&lt;p&gt;📦 &lt;a href="https://pypi.org/project/e2llm-medsynth/" rel="noopener noreferrer"&gt;e2llm-medsynth on PyPI&lt;/a&gt; · &lt;a href="https://github.com/e2llm/medsynth" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;A researcher types in Hebrew:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;מצא חולי סוכרת מעל גיל 60 שמעשנים&lt;br&gt;
&lt;em&gt;(Find diabetic patients over 60 who smoke)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent discovers schemas, builds field maps, explains that Hospital B doesn't track smoking (probable matches only via clinical text), then triggers the workflow.&lt;/p&gt;

&lt;p&gt;Result: 96 patients from 10 indices, classified by confidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strict&lt;/strong&gt;: 36 (structured field match)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Probable&lt;/strong&gt;: 60 (semantic similarity of clinical text)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The researcher continues:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;באילו מחלקות טופלו?&lt;br&gt;
&lt;em&gt;(Which departments were they treated in?)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent queries the cohort index directly with ES|QL — no re-processing of raw data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;הראה לי את ההתאמות הסבירות - למה הן לא ודאיות?&lt;br&gt;
&lt;em&gt;(Show me the probable matches — why aren't they certain?)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent queries &lt;code&gt;match_confidence: "probable"&lt;/code&gt; and returns per-patient explanations with kNN scores and match reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
bash scripts/demo_setup.sh &lt;span class="nt"&gt;--embed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open Kibana → Agent Builder → Medical Cohort Agent → ask a question.&lt;/p&gt;

&lt;p&gt;Two open-source tools came out of this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 &lt;strong&gt;Medical Cohort Agent&lt;/strong&gt;: &lt;a href="https://github.com/e2llm/medical-cohort-agent" rel="noopener noreferrer"&gt;REPO LINK&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧬 &lt;strong&gt;MedSynth&lt;/strong&gt; (synthetic data generator): &lt;a href="https://github.com/e2llm/medsynth" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://pypi.org/project/e2llm-medsynth/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Built with Elasticsearch Agent Builder + Elastic Workflows for the &lt;a href="https://elasticsearch.devpost.com/" rel="noopener noreferrer"&gt;Elasticsearch Agent Builder Hackathon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>healthcare</category>
      <category>ai</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #11 — The Design Loop: From 'Make It Like That Site' to Pixel-Perfect Code</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Sun, 11 Jan 2026 17:03:14 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-11-the-design-loop-from-make-it-like-that-site-to-pixel-perfect-code-54g8</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-11-the-design-loop-from-make-it-like-that-site-to-pixel-perfect-code-54g8</guid>
      <description>&lt;h2&gt;
  
  
  The Problem We All Know
&lt;/h2&gt;

&lt;p&gt;"Make it like that site" — every frontend developer has heard this phrase. A client sends a link to a competitor's landing page. A PM points at an Amazon product card. A designer nods toward &lt;a href="https://dribbble.com" rel="noopener noreferrer"&gt;Dribbble&lt;/a&gt;. And then the guessing game begins.&lt;/p&gt;

&lt;p&gt;Here's what we usually do:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Screenshot → LLM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop an image into &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; or &lt;a href="https://chat.openai.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; asking it to "recreate this." The result? The LLM &lt;em&gt;guesses&lt;/em&gt; styles. Is that padding 16px or 24px? Is that blue &lt;code&gt;#3b82f6&lt;/code&gt; or &lt;code&gt;#2563eb&lt;/code&gt;? Is the shadow &lt;code&gt;0 2px 8px&lt;/code&gt; or &lt;code&gt;0 4px 12px&lt;/code&gt;? Pure hallucination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: DevTools → Copy Styles&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open inspector, right-click, "Copy styles." You get 200 properties including &lt;code&gt;-webkit-tap-highlight-color&lt;/code&gt; and &lt;code&gt;orphans: 2&lt;/code&gt;. Good luck extracting what actually matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: Figma Inspect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Great if you have the original &lt;a href="https://figma.com" rel="noopener noreferrer"&gt;Figma&lt;/a&gt; file. But Figma shows &lt;em&gt;designer intent&lt;/em&gt;, not &lt;em&gt;browser reality&lt;/em&gt;. That &lt;code&gt;auto&lt;/code&gt; layout with 16px gap might render completely differently in CSS.&lt;/p&gt;

&lt;p&gt;None of these give you what you actually need: &lt;strong&gt;the exact computed styles as the browser sees them&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design Loop
&lt;/h2&gt;

&lt;p&gt;Here's a workflow that actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│                                                 │
│   Reference UI ──► Snapshot ──► LLM ──► Code    │
│        ▲                                  │     │
│        │                                  ▼     │
│        └─────── Review in Browser ◄───────┘     │
│                                                 │
│                    REPEAT                       │
└─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Iteration 1:&lt;/strong&gt; Capture the reference → LLM generates first version&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iteration 2:&lt;/strong&gt; Capture your result + reference → "find the differences"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iteration N:&lt;/strong&gt; Refine until pixel-perfect&lt;/p&gt;

&lt;p&gt;The key insight: instead of screenshots, we capture &lt;strong&gt;structured snapshots&lt;/strong&gt; of computed styles. Tools like &lt;a href="https://insitu.im/e2llm" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt; can extract the actual CSS the browser computed — not what the developer wrote, not what Figma intended, but what pixels actually rendered. I covered the underlying schema in &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-7-inside-sifr-the-schema-that-makes-llms-see-web-uis-4p5j"&gt;Runtime Snapshots #7 — Inside SiFR: The Schema That Makes LLMs See Web UIs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Structured Snapshots Beat Screenshots
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Screenshot&lt;/th&gt;
&lt;th&gt;Structured Snapshot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM guesses: padding 16px?&lt;/td&gt;
&lt;td&gt;Exact: &lt;code&gt;padding: 24px 32px&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Looks kinda blue"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#3b82f6&lt;/code&gt; with &lt;code&gt;opacity: 0.9&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can't see hover states&lt;/td&gt;
&lt;td&gt;&lt;code&gt;actionType: "clickable"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No hierarchy info&lt;/td&gt;
&lt;td&gt;Full DOM tree with nesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font looks bold-ish&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;font-weight: 600&lt;/code&gt;, &lt;code&gt;font-family: Inter&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you give an LLM structured data instead of pixels, it stops guessing and starts &lt;em&gt;implementing&lt;/em&gt;. This is the same principle behind &lt;a href="https://dev.to/alexey_sokolov_10deecd763/why-static-code-analysis-falls-short-3a7c"&gt;why static code analysis falls short&lt;/a&gt; — runtime state matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: Cloning a Product Card
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Client points at an Amazon product card — "I want exactly this."&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Capture the reference
&lt;/h3&gt;

&lt;p&gt;Using &lt;a href="https://chromewebstore.google.com/detail/e2llm-element-to-llm/pflhgefnknamlgopnjgdmikepgdonhac" rel="noopener noreferrer"&gt;E2LLM browser extension&lt;/a&gt;, click on the element to get:&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;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&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;"padding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"borderRadius"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"boxShadow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 2px 8px rgba(0,0,0,0.1)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"backgroundColor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#ffffff"&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;"children"&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;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"img"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&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;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"aspectRatio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"objectFit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contain"&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;span class="nl"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"h3"&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;"Product Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"computedStyles"&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;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fontWeight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"400"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lineHeight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0f1111"&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;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;
  
  
  Step 2: Prompt the LLM
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a React component ProductCard with these exact styles. Use &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; where classes match exactly, inline styles for precise values that don't map to Tailwind."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3: LLM generates code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;image&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="nx"&gt;price&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-4 rounded-lg bg-white"&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;boxShadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 2px 8px rgba(0,0,0,0.1)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full aspect-square object-contain"&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm font-normal leading-snug"&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#0f1111&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  Step 4: Capture your result
&lt;/h3&gt;

&lt;p&gt;Open your component in the browser, capture it the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Diff prompt
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Compare these two snapshots. First is the reference, second is my implementation. List style differences and suggest fixes."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LLM responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Differences found:
- lineHeight: reference 1.4 vs yours 1.375 (leading-snug)
- Consider using style={{ lineHeight: '1.4' }} for exact match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Repeat until the diff is empty.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diff Mode: Two Pages Side by Side
&lt;/h2&gt;

&lt;p&gt;For complex pages, you can capture both simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reference Site          Your Implementation
      │                         │
      ▼                         ▼
  [Capture]                 [Capture]
      │                         │
      └────────► LLM ◄──────────┘
                  │
                  ▼
          "Differences:
           - padding: 24px vs 16px
           - font-weight: 600 vs 400
           - border-radius: 12px vs 8px"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns subjective "looks close enough" into objective "here are the 3 remaining differences." Similar to how we use snapshots for &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-ge4"&gt;semantic regression detection&lt;/a&gt;, but for design fidelity instead of functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Loop Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measurable progress&lt;/strong&gt; — each iteration shrinks the diff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Objectivity&lt;/strong&gt; — no more "it looks similar," just concrete numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — snapshots save as artifacts for future reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-context learning&lt;/strong&gt; — the LLM learns from its mistakes within the session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the same loop pattern I described in &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-44b1"&gt;Runtime Snapshots #10 — The Loop&lt;/a&gt;, applied to design workflow instead of QA.&lt;/p&gt;

&lt;h2&gt;
  
  
  When This Is Especially Useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agency work:&lt;/strong&gt; Client shows competitor's site as reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redesigns:&lt;/strong&gt; Updating UI while preserving the "feel"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design systems:&lt;/strong&gt; Extracting tokens from existing UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive work:&lt;/strong&gt; Capture at different viewports → LLM adapts breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://chromewebstore.google.com/detail/e2llm-element-to-llm/pflhgefnknamlgopnjgdmikepgdonhac" rel="noopener noreferrer"&gt;E2LLM for Chrome&lt;/a&gt; (also available for &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/e2llm/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Open any site you want to recreate&lt;/li&gt;
&lt;li&gt;Click the extension icon → Select Element → click a component&lt;/li&gt;
&lt;li&gt;Paste the JSON into &lt;a href="https://claude.ai" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; or &lt;a href="https://chat.openai.com" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt;: "Recreate this as a React component"&lt;/li&gt;
&lt;li&gt;Render your result, capture it&lt;/li&gt;
&lt;li&gt;Send both snapshots: "Find the differences"&lt;/li&gt;
&lt;li&gt;Fix and repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The gap between "reference" and "implementation" becomes a number that shrinks to zero. That's the Design Loop.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of the &lt;a href="https://dev.to/alexey_sokolov_10deecd763/series/29498"&gt;Runtime Snapshots&lt;/a&gt; series, exploring how structured browser data changes the way we work with LLMs for frontend development. Check out &lt;a href="https://insitu.im/e2llm" rel="noopener noreferrer"&gt;E2LLM&lt;/a&gt; to try it yourself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>e2llm</category>
      <category>cicd</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>🧩 Runtime Snapshots #10 — The Loop: E2LLM in Production</title>
      <dc:creator>Alechko</dc:creator>
      <pubDate>Fri, 09 Jan 2026 08:53:19 +0000</pubDate>
      <link>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-2o66</link>
      <guid>https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-10-the-loop-e2llm-in-production-2o66</guid>
      <description>&lt;p&gt;&lt;strong&gt;Your CI is green. Your users can't checkout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the story of every team that trusted tests more than reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With "Ship and Pray"
&lt;/h2&gt;

&lt;p&gt;Traditional deployment flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code → Tests → Deploy → Hope → (Bug report 3 days later)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests check if code works. They don't check if users can &lt;em&gt;use&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;A promo banner covering the checkout button? Tests pass.&lt;br&gt;&lt;br&gt;
Mobile layout pushing CTA below the fold? Tests pass.&lt;br&gt;&lt;br&gt;
Third-party widget blocking the form? Tests pass.&lt;/p&gt;

&lt;p&gt;All green. Ship it. Break production.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Loop
&lt;/h2&gt;

&lt;p&gt;We built a different flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHIP  --&amp;gt;  CAPTURE  --&amp;gt;  INTERPRET  --&amp;gt;  FIX  --&amp;gt;  REPEAT
  |           |              |            |           |
  v           v              v            v           v
Code hits   E2LLM        LLM analyzes  Actionable  Every deploy,
production  snapshots    with context  specific    every page
            real UI                    guidance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No screenshots. No guessing. Real state → Real answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Ship
&lt;/h2&gt;

&lt;p&gt;You merge. CI runs. Tests pass. Deploy triggers.&lt;/p&gt;

&lt;p&gt;Nothing new here. The difference is what happens next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Capture
&lt;/h2&gt;

&lt;p&gt;Post-deploy, E2LLM captures what users actually see.&lt;/p&gt;

&lt;p&gt;Not the source code.&lt;br&gt;&lt;br&gt;
Not the accessibility tree.&lt;br&gt;&lt;br&gt;
Not a screenshot.&lt;/p&gt;

&lt;p&gt;The rendered DOM—with salience scoring.&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;"page"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/checkout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"375x667"&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;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#pay-button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"occluded_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#promo-banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"occlusion_percent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#promo-banner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"salience"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"z_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&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;This is &lt;strong&gt;SiFR&lt;/strong&gt;—Structured Interface Representation.&lt;br&gt;&lt;br&gt;
~400KB instead of megabytes. Zero schema overhead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: Interpret
&lt;/h2&gt;

&lt;p&gt;Feed the snapshot to any LLM. No special integration needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze this checkout page snapshot.
Identify any UX blockers for completing purchase.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CRITICAL: Pay button (#pay-button) is 60% occluded by
promo banner (#promo-banner) on mobile viewport.

Users cannot reliably tap the primary CTA.

Suggested fix: Add margin-bottom to promo banner
or reduce z-index below checkout form.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model doesn't guess. It sees what the user sees.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Fix
&lt;/h2&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What&lt;/strong&gt; is broken (button occluded)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why&lt;/strong&gt; it's broken (z-index, fixed positioning)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How&lt;/strong&gt; to fix it (specific CSS change)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where&lt;/strong&gt; it happens (mobile viewport only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not "button might be hard to click."&lt;br&gt;&lt;br&gt;
Not "consider checking mobile layout."&lt;/p&gt;

&lt;p&gt;Actionable. Specific. Reproducible.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Repeat
&lt;/h2&gt;

&lt;p&gt;This isn't a one-time debug session. It's a loop:&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;// Post-deploy hook&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;)&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;page&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;pages&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;snapshot&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;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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;analysis&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;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&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;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasBlockers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blockers&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;Every deploy. Every critical page. Automated.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Catches
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          E2E Tests    Visual Diff    The Loop
                          ---------    -----------    --------
Button covered by banner     Pass         Maybe        ALERT
CTA pushed off viewport      Pass         Pass         ALERT
Mobile-only layout break     Miss         Noise        ALERT
Third-party widget overlap   Pass         Pass         ALERT
Font size change            Ignore       500 alerts    Ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference: we check &lt;strong&gt;meaning&lt;/strong&gt;, not pixels.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration Points
&lt;/h2&gt;

&lt;p&gt;The Loop fits into existing infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-ci.yml&lt;/span&gt;
&lt;span class="na"&gt;post_deploy_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;verify&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2llm capture --pages /checkout,/signup,/dashboard&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;e2llm analyze --alert-on critical&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Playwright:&lt;/strong&gt;&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/checkout&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;snapshot&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt;&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;// Cron: every hour&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&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;/checkout&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;/signup&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;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e2llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkAndAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Old:&lt;/strong&gt; "Tests pass, we're good."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;New:&lt;/strong&gt; "Tests pass, now let's check reality."&lt;/p&gt;

&lt;p&gt;Tests verify intent.&lt;br&gt;&lt;br&gt;
The Loop verifies experience.&lt;/p&gt;

&lt;p&gt;Both matter. But only one catches the banner blocking your checkout.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ship → Capture → Interpret → Fix → Repeat&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt; real UI state with E2LLM (salience-scored, token-efficient)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interpret&lt;/strong&gt; with any LLM (Claude, GPT, Llama—your choice)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt; with actionable, specific guidance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat&lt;/strong&gt; on every deploy, automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your tests check if code works.&lt;br&gt;&lt;br&gt;
The Loop checks if users can use it.&lt;/p&gt;




&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://insitu.im/e2llm/" rel="noopener noreferrer"&gt;e2llm.dev&lt;/a&gt;&lt;/strong&gt; — Free, open source, installs in seconds.&lt;/p&gt;

&lt;p&gt;📖 Previous in series: &lt;a href="https://dev.to/alexey_sokolov_10deecd763/runtime-snapshots-9-semantic-regression-detection-when-deploys-break-ux-not-tests-3cgo"&gt;Runtime Snapshots #9 — SiFR Deep Dive&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #e2llm #qa #testing #devops #frontend #cicd #webdev&lt;/em&gt;&lt;/p&gt;

</description>
      <category>e2llm</category>
      <category>cicd</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
