<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Zahi Raifer]]></title><description><![CDATA[Zahi Raifer]]></description><link>https://zahiraifer.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 03 Nov 2026 23:15:58 GMT</lastBuildDate><atom:link href="https://zahiraifer.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Six Calls and a Commit: Budgeting LLM Turns When an AI Edits Live Websites]]></title><description><![CDATA[By Zahi Raifer When you let a language model change a website that real customers are visiting, the hard questions are mostly not about prompting. They are…]]></description><link>https://zahiraifer.hashnode.dev/six-calls-and-a-commit-budgeting-llm-turns-when-an-ai-edits-live-websites</link><guid isPermaLink="true">https://zahiraifer.hashnode.dev/six-calls-and-a-commit-budgeting-llm-turns-when-an-ai-edits-live-websites</guid><category><![CDATA[architecture]]></category><category><![CDATA[Python]]></category><category><![CDATA[llm]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Zahi Raifer]]></dc:creator><pubDate>Mon, 26 Oct 2026 07:49:41 GMT</pubDate><content:encoded><![CDATA[<p><em>By Zahi Raifer</em></p>
<p>When you let a language model change a website that real customers are visiting, the hard questions are mostly not about prompting. They are about limits. How many times may the model think before it has to act? How much does it see on each call? And what happens when it gets something wrong?</p>
<p>At Orivox we answer those three questions with three blunt mechanisms: a fixed call budget per turn, a predictable context assembled the same way every time, and a Git commit after every turn. This post goes through each one, with the numbers we measured.</p>
<h2>The shape of one turn</h2>
<p>An edit "turn" starts when a site owner types a request, in Hebrew or English, such as "make the FAQ cards dark". The web app checks the account, queues the job and takes a project lock. The editing service puts the page together in memory, with the shared header and footer included, and asks the planner model what to do.</p>
<p>The planner has two ways to respond. It can answer in plain words, or it can call a tool. There are 11 tools. The important one takes a list of small, named operations that target elements by a stable id. Python applies those operations, runs its checks, saves the page and commits the result. The model itself never writes a file.</p>
<p>Everything below depends on that split. The model proposes, and deterministic code decides.</p>
<h2>A budget of six model calls</h2>
<p>Only some tools end a turn. An edit call does. Every other tool returns a result to the planner so it can keep going: reading another page, looking at a screenshot of the rendered page, reading a page the owner linked to (with their consent), pulling a palette from an image, generating an image, and so on.</p>
<p>That is useful, and it is also how an agentic loop wanders off: a planner that can always ask for one more screenshot can burn calls on a request that needed one class change.</p>
<p>So each turn has a fixed budget of <strong>six model calls</strong>. Inside that budget the planner can gather what it needs: check the other page, look at the render, then emit its operations. When the budget runs out, the turn ends.</p>
<p>In practice, a hard cap has a few effects:</p>
<ul>
<li><strong>Cost per turn has a ceiling.</strong> You can work out the worst case before you ship, not after the bill arrives.</li>
<li><strong>Latency has a ceiling too.</strong> Turns run in Redis-backed job queues, so a web request never waits on a model. A slow call can't tie up the site, and a bounded loop can't hold a project lock forever.</li>
<li><strong>It pushes the design toward cheap, decisive actions.</strong> If the useful information isn't reachable in a few calls, the fix is usually a better tool or better context. Raising the cap rarely helps.</li>
</ul>
<h2>What fits in about 42K tokens</h2>
<p>The second limit is context. On every turn the planner gets a full, predictable request of about <strong>42,000 input tokens</strong>, built the same way each time. Here is the breakdown, measured with Gemini's own token count:</p>
<table>
<thead>
<tr>
<th>Part of the request</th>
<th>Tokens</th>
</tr>
</thead>
<tbody><tr>
<td>System prompt (the editing rules)</td>
<td>22,893</td>
</tr>
<tr>
<td>11 tool definitions</td>
<td>3,538</td>
</tr>
<tr>
<td>The page, header and footer inlined (median of 15 real pages)</td>
<td>16,273 (range 6,302 to 17,767)</td>
</tr>
<tr>
<td>Last five turns of conversation (median)</td>
<td>about 1,100</td>
</tr>
<tr>
<td>The request itself</td>
<td>about 180</td>
</tr>
</tbody></table>
<p>Two things stand out.</p>
<p>First, the owner's actual request is a rounding error. Nearly all the tokens go to rules, tools and the page. Context engineering here mostly means deciding how the page is represented.</p>
<p>Second, the rules and tool definitions add up to about <strong>26,000 tokens</strong> and are identical on every turn. Because they never change, they are cached, so the fixed part of each request is fast and inexpensive. If you build something similar, put the stable material at the front and keep it byte-for-byte stable. Caching only helps if the prefix really is the same each time.</p>
<h2>Raw HTML beat the clever summary</h2>
<p>I expected a compact JSON summary of the page (element ids, roles, short text) to beat raw HTML. It is the kind of preprocessing that seems obviously right.</p>
<p>We benchmarked both on real edit requests. Raw HTML gave the planner the same understanding at <strong>about half the tokens</strong> of the JSON summary, and with raw HTML the planner sees every element on the page instead of whatever the summarizer decided to keep.</p>
<p>My guess is that models have seen a huge amount of HTML and none of our private summary format.</p>
<p>The summary is still useful. Very large pages switch to it automatically, so a single oversized page can't take over the context. But it is the fallback, not the default.</p>
<h2>Small answers on purpose</h2>
<p>On the output side, the numbers are much smaller. Across 24 benchmark runs, the median planner output was <strong>141 tokens</strong>. That is a handful of operations, not a rewritten page.</p>
<p>This follows directly from asking for operations instead of documents. A request like "make the FAQ cards dark" turns into a few class changes on specific elements, and nothing else on the page moves. Short outputs are quicker to generate, cheaper, and much easier to check. You can validate, log and replay a list of operations before anything touches disk.</p>
<h2>Git is the undo button</h2>
<p>Budgets limit how much the model can do in one turn. They don't make what it does safe. For that we rely on version control.</p>
<p><strong>Every edit turn is a Git commit</strong> in the project's own repository. Before the first edit, the original site is <strong>committed as a baseline</strong>, so the owner can always get back to the version they started with. Undo is a restore, not a reconstruction. The planner even has a tool for restoring an earlier version, so "go back to how it was yesterday" is a normal request rather than a support ticket.</p>
<p>Some rules keep that history trustworthy:</p>
<ul>
<li><strong>One owner of history.</strong> The editing service owns each project's Git repository. The web app never runs Git against a project. When it has to write files itself, it takes the lock, writes, and asks the editing service to commit.</li>
<li><strong>One writer at a time.</strong> A single project lock, held in Redis, covers the whole turn across every process. Two changes can never interleave on the same site.</li>
<li><strong>Checks before commits.</strong> Every save goes through deterministic Python guards. A save can never empty a live page, moved text keeps every sentence, the business logo and the contact form's privacy notice are protected, and a claim of success needs a real change. Each guard is pinned by a replay test.</li>
</ul>
<p>With a commit per turn, a strange edit isn't an incident. It's one commit you can look at and roll back. The model doesn't need to be perfect. It needs to be bounded, checked, and reversible.</p>
<h2>Takeaways</h2>
<p>If you are putting an LLM in charge of edits to something live, here is what I would carry over:</p>
<ol>
<li><strong>Cap the loop.</strong> Choose a model-call budget per turn and enforce it in code.</li>
<li><strong>Measure your context.</strong> Count tokens per component. You will probably find the user's request is the smallest part.</li>
<li><strong>Cache the stable prefix.</strong> Rules and tool schemas that don't change should be cached.</li>
<li><strong>Benchmark your representation.</strong> Our "smarter" summary was worse than raw HTML at about twice the cost.</li>
<li><strong>Ask for operations, not artifacts.</strong> Small outputs can be validated and replayed.</li>
<li><strong>Commit every turn.</strong> Commit a baseline first, then one commit per change, with a single owner of the history.</li>
</ol>
<p>I spent many years in product development before building this, and the lesson I keep coming back to hasn't changed: build for the bad day. On a bad day, the budget, the guards and the Git log keep the problem small.</p>
<p>This is how the editor at <a href="https://orivox.org">Orivox</a> works today, and I'm happy to compare notes with anyone building something similar.</p>
<p><em>Disclosure: AI assisted in drafting this article from the author's technical write-up; the author reviewed and edited the final text.</em></p>
<hr />
<p><em>Zahi Raifer is a software developer with 23 years of experience in AI development, Python, .NET and databases, and the founder of Orivox.</em></p>
]]></content:encoded></item><item><title><![CDATA[Exposing a website builder to AI agents via MCP: what an MCP server for site generation looks like]]></title><description><![CDATA[Tools, auth, long-running jobs and guardrails: a conceptual walkthrough of turning a site builder into something an agent can drive.]]></description><link>https://zahiraifer.hashnode.dev/exposing-a-website-builder-to-ai-agents-via-mcp-what-an-mcp-server-for-site-generation-looks-like</link><guid isPermaLink="true">https://zahiraifer.hashnode.dev/exposing-a-website-builder-to-ai-agents-via-mcp-what-an-mcp-server-for-site-generation-looks-like</guid><category><![CDATA[MCPAI Agents]]></category><category><![CDATA[llm]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[API Design]]></category><dc:creator><![CDATA[Zahi Raifer]]></dc:creator><pubDate>Fri, 23 Oct 2026 08:43:49 GMT</pubDate><content:encoded><![CDATA[<p>For most of the web's history, a website builder had exactly one kind of user: a person sitting in front of a dashboard. Drag a block here, change a color there, click Publish. The Model Context Protocol (MCP) adds a second kind of user, an AI agent, and that changes what "the product surface" means.</p>
<p>This is a conceptual tour for developers curious about exposing something stateful and slightly messy, like a website, to an agent. I'll stay at the level of protocol concepts and design decisions, not any one vendor's implementation.</p>
<h2>A 60-second refresher on MCP</h2>
<p>MCP is an open protocol that standardizes how an AI client (a chat app, an IDE, an agent runtime) talks to external capabilities. A server exposes three main kinds of things:</p>
<ul>
<li><strong>Tools</strong>: functions the model can call, each with a name, a description, and a JSON Schema for its inputs.</li>
<li><strong>Resources</strong>: readable data the client can pull into context, addressed by URI.</li>
<li><strong>Prompts</strong>: reusable, parameterized templates a user can invoke.</li>
</ul>
<p>Servers run locally over stdio or remotely over HTTP (Streamable HTTP in the current spec). For a hosted site builder, remote is the natural choice: nothing to install, just a URL. Remote servers authenticate users with OAuth, and since many MCP clients are public clients that can't keep a secret, PKCE is the usual flow.</p>
<h2>Designing the tool surface</h2>
<p>The temptation is to mirror your REST API one-to-one. Resist it. A REST API is designed for programmers who read docs; a tool list is designed for a model that reads descriptions and guesses. A few principles that matter more than usual:</p>
<p><strong>Name tools after intentions, not endpoints.</strong> <code>create_site_from_brief</code> tells the model when to use it. <code>POST /v2/projects</code> does not.</p>
<p><strong>Write descriptions as instructions.</strong> The description is effectively part of the prompt. Say what the tool does, when to prefer it over a sibling, and what the model should do with the result ("return the preview URL to the user").</p>
<p><strong>Choose granularity deliberately.</strong> A site builder could expose one giant <code>build_website</code> tool, or forty tiny ones (<code>set_heading_text</code>, <code>set_button_color</code>...). Too coarse and the agent can't make targeted edits. Too fine and it burns turns and context on trivia. A workable middle ground for site generation tends to look something like:</p>
<ul>
<li>create a site from a natural-language brief</li>
<li>list and inspect a user's sites</li>
<li>read the current structure of a page</li>
<li>apply an edit, described in natural language or as a structured patch</li>
<li>generate a preview link</li>
<li>publish or unpublish</li>
<li>connect a domain</li>
</ul>
<p><strong>Return things the model can act on.</strong> After a create or edit call, the most useful payload is short: an ID, a status, and a preview URL. Dumping the whole generated HTML back into context is usually a waste of tokens.</p>
<h2>Generation is slow, so plan for it</h2>
<p>Tutorial tools return instantly. Building a website does not. Depending on complexity, generation can take anywhere from several seconds to a minute or more.</p>
<p>You have a few options:</p>
<ol>
<li><strong>Block and wait.</strong> Fine if generation reliably finishes within the client's timeout.</li>
<li><strong>Job pattern.</strong> Return a job ID and expose a <code>get_job_status</code> tool. Agents are not always patient pollers, though.</li>
<li><strong>Progress notifications.</strong> MCP supports progress updates for long-running requests, which lets a client show something better than a spinner.</li>
</ol>
<p>Whichever you pick, make the tool description explicit about it. A model that doesn't know a build is asynchronous will cheerfully tell the user "your site is live!" before anything exists.</p>
<h2>Guardrails for actions with consequences</h2>
<p>Some operations in a site builder are harmless (generate a draft). Some are public (publish to the open web). Some cost money (register a domain). An agent shouldn't treat them the same way.</p>
<p>Patterns worth adopting:</p>
<ul>
<li><strong>Separate draft from publish.</strong> Generation and editing should never implicitly make anything public.</li>
<li><strong>Make consequences visible in the schema.</strong> A <code>publish</code> tool that requires a <code>confirm: true</code> argument, plus a description stating that the user must approve, nudges the model to ask first.</li>
<li><strong>Lean on client-side approval.</strong> Many MCP clients ask the user before running a tool. Mark destructive or costly tools clearly so clients and users can treat them differently.</li>
<li><strong>Scope OAuth tokens narrowly.</strong> If a token can only touch the sites belonging to the user who authorized it, a confused agent can do far less damage.</li>
<li><strong>Validate server-side anyway.</strong> Generated content still goes through the same checks as content from a human editor. The model is a user, not a trusted component.</li>
</ul>
<h2>Errors are part of the conversation</h2>
<p>When a tool call fails, a model reads the error and decides what to do next. That makes error messages a design surface.</p>
<p>"400 Bad Request" teaches the model nothing. "The domain example.com is already connected to another site. Ask the user whether to move it." gives the agent a sensible next step. MCP lets a tool return an error result as regular content, which is often better than a protocol-level failure precisely because the model gets to see and reason about it.</p>
<h2>What people actually do with it</h2>
<p>Once a site builder speaks MCP, a few use cases fall out naturally:</p>
<ul>
<li><strong>Conversation-first creation.</strong> Someone already chatting with an assistant about their new bakery can ask for a website without switching apps.</li>
<li><strong>Agent workflows.</strong> An agent setting up a small business (drafting a menu, writing a Google Business description) can add "publish a one-page site" as one more step.</li>
<li><strong>Agencies and freelancers.</strong> Spinning up first drafts for several clients from a list of briefs, then refining each by hand.</li>
</ul>
<h2>A real-world example</h2>
<p>This isn't hypothetical. Orivox, a Hebrew-first AI website builder, runs a remote MCP server at mcp.orivox.org, and it's listed in public MCP directories. You add it to an MCP-capable client as a connector, sign in, and then ask for a site in plain language. If you want to see the product the connector sits on top of, here's the <a href="https://orivox.org">Orivox AI website builder</a>.</p>
<h2>Takeaways</h2>
<p>Treat tool names and descriptions as prompt engineering, design for slow operations from day one, keep a hard line between drafting and anything public or paid, and write errors that tell the model what to do next. The protocol itself is small. Most of the craft is in deciding what an agent should be able to do, and what it should have to ask permission for.</p>
]]></content:encoded></item></channel></rss>