<?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: Sayed Ali Alkamel</title>
    <description>The latest articles on DEV Community by Sayed Ali Alkamel (@sayed_ali_alkamel).</description>
    <link>https://dev.to/sayed_ali_alkamel</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%2F2652218%2F63a5dfd1-8229-48c1-85eb-54a58560297f.jpg</url>
      <title>DEV Community: Sayed Ali Alkamel</title>
      <link>https://dev.to/sayed_ali_alkamel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sayed_ali_alkamel"/>
    <language>en</language>
    <item>
      <title>Prompt Chaining: Fixed Steps That Each Feed the Next</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:36:33 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/prompt-chaining-fixed-steps-that-each-feed-the-next-4jkb</link>
      <guid>https://dev.to/sayed_ali_alkamel/prompt-chaining-fixed-steps-that-each-feed-the-next-4jkb</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short version:&lt;/strong&gt; Prompt chaining splits a task into a fixed sequence of steps, where each model call works on the output of the last. Anthropic calls it prompt chaining, and Google calls it the sequential pipeline. It trades speed for accuracy, and it is the easiest agent pattern to debug because you always know where the data came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is prompt chaining?
&lt;/h2&gt;

&lt;p&gt;Prompt chaining decomposes a task into a sequence of steps, where each LLM call processes the output of the previous one (&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;). Google frames the same shape as a sequential pipeline: specialized agents run in a fixed, linear order, and the output of one agent is the direct input to the next (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Think of an assembly line. A parser turns a raw PDF into text, an extractor pulls out structured fields, and a summarizer writes the synopsis (&lt;a href="https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/" rel="noopener noreferrer"&gt;Google Developers&lt;/a&gt;). Each station does one job well.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4u0aqcyxz26rmw38tlz.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4u0aqcyxz26rmw38tlz.gif" alt="Diagram: a four step pipeline, draft copy, a gate that checks the work, translate, then the final output, with a signal flowing through each box." width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate: your cheapest safeguard
&lt;/h2&gt;

&lt;p&gt;Anthropic adds one detail that makes chaining reliable: a gate. Between steps you can add a programmatic check that confirms the process is still on track before it continues (&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;). If the outline fails a criterion, you stop before wasting a call on a bad draft.&lt;/p&gt;

&lt;p&gt;A gate is not a model call. It is plain code: a length check, a schema validation, a regex, a test. That is why chaining is cheap to make safe.&lt;/p&gt;

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

&lt;p&gt;State is the whole trick. In Google ADK, each step writes its result to a shared session state with an output key, and the next agent reads from that key (&lt;a href="https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/" rel="noopener noreferrer"&gt;Google Developers&lt;/a&gt;). A sequential workflow agent runs the steps in order and does not consult a model to decide what comes next, which keeps it predictable and cheap (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;).&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="c1"&gt;# Google ADK, sketch
&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;extractor&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extractor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use {raw_text}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;summarizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarizer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use {fields}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pipeline&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SequentialAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extractor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summarizer&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to use it
&lt;/h2&gt;

&lt;p&gt;Use prompt chaining when the task splits cleanly into fixed subtasks and you want higher accuracy per step (&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;). Classic examples: write marketing copy, then translate it. Or write an outline, check it against criteria, then write the document from the approved outline.&lt;/p&gt;

&lt;p&gt;It shines for highly structured, repeatable processes where the order never changes, such as a data extraction, cleaning, and loading pipeline (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;). The payoff is that each call is an easier task, so the model makes fewer mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;p&gt;Do not chain when the steps are not known in advance. If the number and nature of subtasks depend on the input, the rigid pipeline cannot adapt, and you want orchestrator-workers instead. Do not chain when latency is the priority and the subtasks are independent. Run them in parallel. And if a single call already produces a good answer, chaining just adds round trips for nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known problems
&lt;/h2&gt;

&lt;p&gt;The trade-off is rigidity. Google is explicit: the fixed structure makes it hard to adapt to dynamic conditions or to skip unneeded steps, which can cause inefficient processing or push up cumulative latency if a step that was not needed is slow (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;There is also error propagation. Because each step consumes the last step's output, a mistake early in the chain flows downstream. Gates catch some of this, but a step that produces plausible-but-wrong output can still poison everything after it. And every extra step adds latency, so a long chain feels slow even when each call is fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to know before you start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Put a gate after any risky step.&lt;/strong&gt; A cheap code check beats an expensive bad draft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name your state clearly.&lt;/strong&gt; Downstream steps read named outputs, so a vague key becomes a silent bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the chain short.&lt;/strong&gt; Each link adds latency and one more place to fail. If you need branching, you have outgrown a straight chain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is prompt chaining the same as a sequential pipeline?&lt;/strong&gt;&lt;br&gt;
Yes. Anthropic uses "prompt chaining" and Google uses "sequential pipeline" for the same fixed, linear flow where each step feeds the next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does chaining need multiple agents?&lt;/strong&gt;&lt;br&gt;
Not necessarily. It can be several prompts to one model or several specialized agents. What defines it is the fixed order and the handoff of output to input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a gate in prompt chaining?&lt;/strong&gt;&lt;br&gt;
A programmatic check between steps that confirms the work is on track before continuing. It is code, not a model call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should I switch to orchestrator-workers?&lt;/strong&gt;&lt;br&gt;
When you cannot predict the steps ahead of time. Chaining is for fixed sequences. Orchestrator-workers decides the subtasks at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, Building Effective Agents: &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;https://www.anthropic.com/engineering/building-effective-agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Developers Blog, Developer's guide to multi-agent patterns in ADK: &lt;a href="https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/" rel="noopener noreferrer"&gt;https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud Architecture Center, Choose a design pattern for your agentic AI system: &lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>workflows</category>
    </item>
    <item>
      <title>The Augmented LLM: The Building Block Every Agent Sits On</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:53:34 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/the-augmented-llm-the-building-block-every-agent-sits-on-4hdm</link>
      <guid>https://dev.to/sayed_ali_alkamel/the-augmented-llm-the-building-block-every-agent-sits-on-4hdm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short version:&lt;/strong&gt; The augmented LLM is a single model given three extra abilities: retrieval, tools, and memory. Anthropic calls it the basic building block of every agentic system. Google calls the same idea a single-agent system. Master this before you touch multi-agent designs, because every other pattern is built on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the augmented LLM?
&lt;/h2&gt;

&lt;p&gt;The augmented LLM is a language model enhanced with retrieval, tools, and memory, where the model itself drives those abilities: it writes its own search queries, picks the right tool, and decides what to keep in context (&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;). Google describes the equivalent single-agent system as one model, a defined set of tools, and a system prompt that lets the agent interpret a request, plan steps, and choose tools on its own (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The important word is augmented. A bare model answers from its weights. An augmented model can pull in fresh facts, act on the world, and remember across turns.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3tj34pkvuc18h1e1ga1.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3tj34pkvuc18h1e1ga1.gif" alt="Diagram: a central LLM node connected to three satellites, retrieval, tools, and memory, with signals flowing out to each." width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The three augmentations
&lt;/h2&gt;

&lt;p&gt;Retrieval lets the model fetch information it was not trained on. It generates a query, reads the results, and grounds its answer in them. Tools let the model call functions and APIs, then read the output to decide what to do next. Memory lets the model carry context forward, so it does not repeat itself or lose the thread (&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Anthropic's advice on all three is the same: tailor them to your use case, and put a clean, well-documented interface in front of them. One way to wire tools in is the Model Context Protocol, which gives the model a standard client for a growing set of third-party tools.&lt;/p&gt;

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

&lt;p&gt;A single augmented agent runs a short loop. It reads the request, decides whether it needs a tool or a search, calls it, reads the result, and either answers or takes another step. There is no second agent and no orchestration layer. The model's own reasoning is the controller.&lt;/p&gt;

&lt;p&gt;That simplicity is the point. Google recommends starting here so you can focus on the three things that actually decide quality: the core logic, the system prompt, and the tool definitions (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;). Anthropic makes the tool interface a first-class concern and suggests spending as much effort on the agent-computer interface as teams spend on human interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use it
&lt;/h2&gt;

&lt;p&gt;Use a single augmented LLM when the task needs external data or actions but still fits one clear responsibility. A customer support agent that looks up an order, or a research helper that calls an API and summarizes the result, are ideal cases. A non-agentic system cannot do these, because it cannot use tools or run a multi-step plan (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It is also the right first move for any prototype. You can ship a single agent in a short time, learn where it breaks, and only then decide whether a heavier pattern earns its cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;p&gt;Skip the augmentation when a single plain model call already answers the request. Summarizing a document, translating text, or classifying feedback usually do not need tools or memory, so an agent adds cost for nothing (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Also move on when one agent starts juggling several distinct jobs. Google's own guidance is blunt: a single agent becomes a jack of all trades and a master of none as you pile on tools and responsibilities (&lt;a href="https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/" rel="noopener noreferrer"&gt;Google Developers&lt;/a&gt;). That is your signal to split into specialists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known problems
&lt;/h2&gt;

&lt;p&gt;The main failure mode is overload. As you add tools and task complexity, a single agent's performance drops: you see higher latency, wrong tool choices, or tasks that never finish (&lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;Google Cloud&lt;/a&gt;). You can often push the limit further with better reasoning structure, such as the ReAct loop, but that only buys headroom, it does not remove the ceiling.&lt;/p&gt;

&lt;p&gt;The second problem is a weak tool interface. If a tool is hard for a person to use from its description alone, it is hard for the model too. Anthropic found it spent more time refining tools than prompts, and that small fixes, like requiring absolute file paths instead of relative ones, removed whole classes of errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to know before you start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The system prompt is the product.&lt;/strong&gt; It defines the agent's task, persona, and the exact conditions for using each tool. Vague prompts produce vague agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document tools like you would for a junior teammate.&lt;/strong&gt; Include example usage, edge cases, and clear boundaries. Test real inputs and iterate on the descriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure before you scale.&lt;/strong&gt; Prove the single agent falls short on real tasks before adding a second agent, because multi-agent designs cost far more to run and maintain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is an augmented LLM the same as an agent?&lt;/strong&gt;&lt;br&gt;
It is the smallest agent. Anthropic treats it as the base building block; Google treats the same shape as a single-agent system. Larger patterns compose several of these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is retrieval-augmented generation (RAG) an augmented LLM?&lt;/strong&gt;&lt;br&gt;
RAG is the retrieval part of it. A full augmented LLM adds tools and memory on top, and lets the model decide when to use each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is this different from a chatbot?&lt;/strong&gt;&lt;br&gt;
A chatbot answers from the model alone. An augmented LLM can fetch live data, call APIs, and act, then use those results in its answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should I move to multiple agents?&lt;/strong&gt;&lt;br&gt;
When one agent must handle several distinct responsibilities, or when adding tools starts to hurt accuracy and latency. That is the point where specialists win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, Building Effective Agents: &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;https://www.anthropic.com/engineering/building-effective-agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud Architecture Center, Choose a design pattern for your agentic AI system: &lt;a href="https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system" rel="noopener noreferrer"&gt;https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Developers Blog, Developer's guide to multi-agent patterns in ADK: &lt;a href="https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/" rel="noopener noreferrer"&gt;https://developers.googleblog.com/developers-guide-to-multi-agent-patterns-in-adk/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Model Context Protocol: &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;https://modelcontextprotocol.io/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>rag</category>
    </item>
    <item>
      <title>The New SDLC for Project Managers: Vibe Coding vs Agentic Engineering</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Sun, 05 Jul 2026 13:40:40 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/the-new-sdlc-for-project-managers-vibe-coding-vs-agentic-engineering-1130</link>
      <guid>https://dev.to/sayed_ali_alkamel/the-new-sdlc-for-project-managers-vibe-coding-vs-agentic-engineering-1130</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short version:&lt;/strong&gt; AI has split software delivery into two modes. Vibe coding is fast and cheap for throwaway work, but expensive to run and risky to ship. Agentic engineering wraps the AI in specifications, tests, and human review, so the output is dependable. If you manage delivery, your estimates, budgets, risk register, and staffing plan all move.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is vibe coding, and why should a project manager care?
&lt;/h2&gt;

&lt;p&gt;Vibe coding is a way of building software where you describe what you want in plain language, accept whatever the AI produces, and paste errors back until it runs. The term was coined by Andrej Karpathy in February 2025 (&lt;a href="https://x.com/karpathy/status/1886192184808149383" rel="noopener noreferrer"&gt;karpathy on X&lt;/a&gt;), who described giving in to the vibes and forgetting the code even exists. It is fast and it feels like magic. It is also, by definition, code that no human has fully read.&lt;/p&gt;

&lt;p&gt;Agentic engineering is the disciplined end of the same idea. The AI still writes the implementation, but inside human-designed specifications, automated tests, and feedback loops that check the work. These are two ends of one spectrum (&lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;). The differentiator is not whether your team uses AI. It is how much verification surrounds the output. Rule of thumb: a weekend prototype can be pure vibe coding, anything a customer depends on needs agentic engineering.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fys1su3mdoc7lv3cvyws7.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fys1su3mdoc7lv3cvyws7.gif" alt="Two-lane diagram comparing vibe coding, an unverified path from prompt to ship, against agentic engineering, a verified path from spec through tests and review to ship." width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes in the software development life cycle?
&lt;/h2&gt;

&lt;p&gt;The phases you know (requirements, design, build, test, review, maintain) do not disappear. Their proportions change. AI compresses implementation hard: work that took weeks can take hours. It does almost nothing for the human-paced parts, which are requirements, architecture, and verification. So the bottleneck moves. Writing code stops being the slow step. Deciding exactly what to build, and confirming it was built correctly, becomes the slow step.&lt;/p&gt;

&lt;p&gt;This leads to what the Google whitepaper calls the factory model: the team's real output is not the code, it is the system that produces the code. A factory manager does not assemble each unit by hand, they design the line and own quality control. Requirements become a live conversation with the AI rather than a document thrown over a wall. Design stays human, because trade-offs (consistency versus availability, build versus buy) depend on business context. Tests and evals become the contract that tells the AI what "correct" means. And legacy code that was once "too risky to touch" becomes safe to modernize.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdtbayxebl2lwr3n46fdy.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdtbayxebl2lwr3n46fdy.gif" alt="Pipeline diagram of the AI-driven SDLC: specs you define, agents build in minutes to hours, tests and evals verify correctness, then human review signs off to ship." width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who does what now: conductors, orchestrators, and the harness
&lt;/h2&gt;

&lt;p&gt;Developers move between two modes. In conductor mode they work in real time with the AI, reviewing each change as it appears, which suits complex or unfamiliar code. In orchestrator mode they define a task, hand it to one or more agents running in the background, and review the result later, which suits well-scoped work like bug fixes, migrations, and test generation. The same person uses both in a day.&lt;/p&gt;

&lt;p&gt;The model itself is only one part. Around it sits the harness: the rule files, tools, sandboxes, and checks that let the model finish real work. A useful equation from the paper is Agent equals Model plus Harness. This matters for a plain reason. When an agent gets something wrong, the instinct is to blame the model, but most failures trace back to a missing tool, a vague rule, or a missing check. Getting the context right (the specs, conventions, and constraints you feed the agent) does more for quality than swapping models. Treat those context files as team assets: reviewed, versioned, and owned by named people.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs: the CapEx and OpEx flip
&lt;/h2&gt;

&lt;p&gt;For a project manager, the money story is the one that lands in a budget. Vibe coding looks cheap because the upfront cost is near zero. The hidden cost is operational. Every prompt spends tokens, and a loop of "fix your own mistake" burns tokens with a low first-pass hit rate. Unstructured AI code becomes a maintenance tax when a bug surfaces months later, and unverified code becomes a security bill in production.&lt;/p&gt;

&lt;p&gt;Agentic engineering reverses this. You pay more upfront to design specs, tests, and context, and the marginal cost of each new feature drops. Two levers keep running costs down. Context engineering means sending a dense, relevant payload instead of the whole codebase every time. Model routing means sending hard tasks to a large model and cheap, routine tasks (a unit test, a small review) to a smaller, cheaper one. The trade is a familiar one: higher capital cost now for lower operating cost later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things for project managers to hold onto
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The 80% problem is an estimation trap.&lt;/strong&gt; An agent can produce roughly the first 80% of a feature quickly. The last 20% (edge cases, error handling, integration, correctness) needs judgment the model often lacks, and that is where the real time goes. Plan schedules around the hard 20%, not the easy 80%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Velocity is not the same as speed.&lt;/strong&gt; In METR's controlled trial, experienced developers using AI took 19% longer while believing they were about 20% faster (&lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;METR&lt;/a&gt;). Time shifts from writing to reviewing and correcting. Measure delivered, verified work, not lines produced or how fast it feels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI amplifies your engineering culture.&lt;/strong&gt; Teams with strong tests, clear standards, and real code review get much more from AI. Teams without them ship faster into a bigger mess. Adoption alone is not a strategy: usage hit record levels in the &lt;a href="https://survey.stackoverflow.co/2025/" rel="noopener noreferrer"&gt;Stack Overflow 2025 survey&lt;/a&gt; even as trust in AI output fell.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is vibe coding safe for production?&lt;/strong&gt;&lt;br&gt;
Not on its own. It is ideal for prototypes and internal tools. For anything customers depend on, add the specs, tests, and review that define agentic engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will AI make my team smaller?&lt;/strong&gt;&lt;br&gt;
It changes the mix more than the size. Smaller teams can take on larger problems, but the scarce skill shifts to specification, evaluation, and architecture, not typing code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the single best first step?&lt;/strong&gt;&lt;br&gt;
Set up a project rules file (an AGENTS.md or equivalent) with your stack, conventions, and hard rules, then write tests and evals before generating code. Together they are the contract with the AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I explain the risk to leadership?&lt;/strong&gt;&lt;br&gt;
Say the team practices agentic engineering, with AI writing code under human-designed constraints and test coverage confirming correctness. That is a very different conversation than "we are vibe coding the payment system."&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Andrej Karpathy, original "vibe coding" post (Feb 2025): &lt;a href="https://x.com/karpathy/status/1886192184808149383" rel="noopener noreferrer"&gt;https://x.com/karpathy/status/1886192184808149383&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Vibe coding, overview and reception (Wikipedia): &lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Vibe_coding&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;METR, "Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity" (Jul 2025): &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stack Overflow 2025 Developer Survey (AI adoption and sentiment): &lt;a href="https://survey.stackoverflow.co/2025/" rel="noopener noreferrer"&gt;https://survey.stackoverflow.co/2025/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Grounding material: Osmani, Saboo, and Kartakis, "The New SDLC With Vibe Coding," Google (May 2026)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>projectmanagement</category>
      <category>ai</category>
      <category>sdlc</category>
      <category>agents</category>
    </item>
    <item>
      <title>Dart FFI for Flutter: Call Native C Directly (Lessons from camera_pro)</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Sat, 04 Jul 2026 02:43:18 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/dart-ffi-for-flutter-call-native-c-directly-lessons-from-camerapro-2oc0</link>
      <guid>https://dev.to/sayed_ali_alkamel/dart-ffi-for-flutter-call-native-c-directly-lessons-from-camerapro-2oc0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short version:&lt;/strong&gt; Dart FFI lets your Flutter app call C functions directly, as a normal synchronous call, instead of passing serialized messages over a platform channel. That is a big deal when you have an existing C or C++ library, or hot per-frame native work. Here is what it is, when to use it, and what I ran into building &lt;a href="https://pub.dev/packages/camera_pro" rel="noopener noreferrer"&gt;&lt;code&gt;camera_pro&lt;/code&gt;&lt;/a&gt;, a camera package with a shared C core.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Dart FFI?
&lt;/h2&gt;

&lt;p&gt;FFI stands for foreign function interface. Per the Dart docs, apps on the Dart Native platform can use the &lt;code&gt;dart:ffi&lt;/code&gt; library to call native C APIs and to read, write, allocate, and deallocate native memory (&lt;a href="https://dart.dev/interop/c-interop" rel="noopener noreferrer"&gt;dart.dev&lt;/a&gt;). In plain terms: you point Dart at a compiled C library, hand it a function signature, and call the function.&lt;/p&gt;

&lt;p&gt;You do not have to write those bindings by hand. For anything beyond a couple of functions, &lt;code&gt;package:ffigen&lt;/code&gt; generates the Dart wrappers straight from your C header files (&lt;a href="https://dart.dev/interop/c-interop" rel="noopener noreferrer"&gt;dart.dev&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;One boundary to remember: &lt;code&gt;dart:ffi&lt;/code&gt; runs only on the Dart Native platform. There is no FFI on Flutter web.&lt;/p&gt;

&lt;h2&gt;
  
  
  FFI or platform channels?
&lt;/h2&gt;

&lt;p&gt;Both let Flutter reach code the Dart SDK does not ship. They work very differently.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;platform channel&lt;/strong&gt; passes messages between Dart and the host platform. Those messages and responses travel asynchronously, and their values are serialized into a binary format on the way across and deserialized on the other side (&lt;a href="https://docs.flutter.dev/platform-integration/platform-channels" rel="noopener noreferrer"&gt;docs.flutter.dev&lt;/a&gt;). Great for occasional calls like reading the battery level or opening a share sheet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dart FFI&lt;/strong&gt; is a direct call into C. No channel, no per-call serialization, and you can share native memory with the C side. That is what makes it the right tool for tight, frequent, CPU-heavy work.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe497z927v22y6bsy1z9s.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe497z927v22y6bsy1z9s.gif" alt="Two ways to reach native code from Dart: a platform channel passes serialized messages asynchronously, while dart:ffi calls C directly and synchronously." width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rule of thumb: reach for a channel when you occasionally call a platform API. Reach for FFI when you wrap an existing C or C++ library, or when you run native code on every frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the native C actually ships
&lt;/h2&gt;

&lt;p&gt;The old friction with FFI was shipping the compiled library. You had to build the &lt;code&gt;.so&lt;/code&gt;, &lt;code&gt;.dylib&lt;/code&gt;, or &lt;code&gt;.dll&lt;/code&gt; per platform and bundle it yourself.&lt;/p&gt;

&lt;p&gt;Build hooks fix that. Formerly called native assets, build hooks let a package carry native code that is transparently built, bundled, and made available at runtime (&lt;a href="https://dart.dev/interop/c-interop" rel="noopener noreferrer"&gt;dart.dev&lt;/a&gt;). As of Flutter 3.38, the Flutter docs recommend the &lt;code&gt;package_ffi&lt;/code&gt; template with build hooks for C interop, and mark the older &lt;code&gt;plugin_ffi&lt;/code&gt; approach as legacy (&lt;a href="https://docs.flutter.dev/platform-integration/ios/c-interop" rel="noopener noreferrer"&gt;docs.flutter.dev&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The moving parts, which are the actual dependencies of &lt;code&gt;camera_pro&lt;/code&gt;:&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc0zj289zy1yse1iror6.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc0zj289zy1yse1iror6.gif" alt="Build pipeline: a C core compiled by a build hook via native_toolchain_c, bindings generated by ffigen, then called through dart:ffi at runtime." width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffigen&lt;/code&gt; writes the Dart bindings from your headers, a build hook using &lt;code&gt;native_toolchain_c&lt;/code&gt; compiles the C at build time, &lt;code&gt;code_assets&lt;/code&gt; and &lt;code&gt;hooks&lt;/code&gt; bundle it, and &lt;code&gt;package:ffi&lt;/code&gt; gives you helpers like &lt;code&gt;Arena&lt;/code&gt; for native memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What FFI looked like in camera_pro
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;camera_pro&lt;/code&gt; is a Flutter camera package built on a shared C and C++ core over Dart FFI. The C core does the pixel work: YUV to RGBA conversion, histogram, focus peaking, zebra, false color, and a waveform monitor, run on each preview frame. The same bit-exact C runs on macOS, iOS, Linux, and Windows behind one hardware abstraction layer.&lt;/p&gt;

&lt;p&gt;The classic FFI shape, which is what &lt;code&gt;ffigen&lt;/code&gt; produces for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:ffi'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:ffi/ffi.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// toDartString, Arena, using&lt;/span&gt;

&lt;span class="c1"&gt;// Names here are illustrative.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DynamicLibrary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'libcamera_pro.dylib'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// .so or .dll elsewhere&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;coreVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lib&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NativeFunction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Utf8&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;()&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;'camera_pro_version'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asFunction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Utf8&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;()&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coreVersion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toDartString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// "0.0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;lookup(...).asFunction()&lt;/code&gt; pattern is the core of &lt;code&gt;dart:ffi&lt;/code&gt; (&lt;a href="https://docs.flutter.dev/platform-integration/macos/c-interop" rel="noopener noreferrer"&gt;docs.flutter.dev&lt;/a&gt;). With build hooks and code assets you can skip &lt;code&gt;DynamicLibrary.open&lt;/code&gt; and mark the binding with the &lt;code&gt;@Native&lt;/code&gt; annotation, letting the toolchain resolve the symbol from the bundled asset (&lt;a href="https://dart.dev/interop/c-interop" rel="noopener noreferrer"&gt;dart.dev&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Why bother? Because the calls are frequent and the payloads are large. On an Apple M1 Pro, the core converts a 1080p YUV420p frame to RGBA in about 0.66 ms. Copying full frames through a serialized channel on every frame would be a poor fit for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to know before you start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C++ needs &lt;code&gt;extern "C"&lt;/code&gt;.&lt;/strong&gt; FFI binds to C symbols, so C++ functions must be exported as C. Also mark them with &lt;code&gt;__attribute__((visibility("default"))) __attribute__((used))&lt;/code&gt; so link-time optimization does not strip them (&lt;a href="https://docs.flutter.dev/platform-integration/macos/c-interop" rel="noopener noreferrer"&gt;docs.flutter.dev&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You own native memory.&lt;/strong&gt; Anything you allocate for the C side, you free. &lt;code&gt;package:ffi&lt;/code&gt; makes this safe with an arena that releases everything when the block ends:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;   &lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arena&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Uint8&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// freed on exit&lt;/span&gt;
     &lt;span class="c1"&gt;// hand buf to C, read results back&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No web.&lt;/strong&gt; Since &lt;code&gt;dart:ffi&lt;/code&gt; is Dart Native only, plan a fallback. &lt;code&gt;camera_pro&lt;/code&gt; uses a conditional import to keep &lt;code&gt;dart:ffi&lt;/code&gt; off the web build and reimplements the same kernels in pure Dart for the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Dart FFI faster than platform channels?&lt;/strong&gt;&lt;br&gt;
For frequent or CPU-heavy calls, usually yes, because it is a direct synchronous call with no per-call serialization. For a rare one-off platform call, the difference does not matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to write the bindings myself?&lt;/strong&gt;&lt;br&gt;
No. &lt;code&gt;package:ffigen&lt;/code&gt; generates them from your C headers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to bundle the &lt;code&gt;.so&lt;/code&gt; or &lt;code&gt;.dylib&lt;/code&gt; myself?&lt;/strong&gt;&lt;br&gt;
No. Build hooks compile and bundle the native code for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Dart FFI work on Flutter web?&lt;/strong&gt;&lt;br&gt;
No. &lt;code&gt;dart:ffi&lt;/code&gt; runs only on the Dart Native platform. Ship a pure-Dart or JS-interop fallback for web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dart, C interop using &lt;code&gt;dart:ffi&lt;/code&gt;: &lt;a href="https://dart.dev/interop/c-interop" rel="noopener noreferrer"&gt;https://dart.dev/interop/c-interop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flutter, platform channels: &lt;a href="https://docs.flutter.dev/platform-integration/platform-channels" rel="noopener noreferrer"&gt;https://docs.flutter.dev/platform-integration/platform-channels&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flutter, C interop and the legacy plugin note: &lt;a href="https://docs.flutter.dev/platform-integration/macos/c-interop" rel="noopener noreferrer"&gt;https://docs.flutter.dev/platform-integration/macos/c-interop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ffi&lt;/code&gt; package (native memory helpers): &lt;a href="https://pub.dev/packages/ffi" rel="noopener noreferrer"&gt;https://pub.dev/packages/ffi&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ffigen&lt;/code&gt; package (binding generator): &lt;a href="https://pub.dev/packages/ffigen" rel="noopener noreferrer"&gt;https://pub.dev/packages/ffigen&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;camera_pro&lt;/code&gt; on pub.dev and GitHub: &lt;a href="https://pub.dev/packages/camera_pro" rel="noopener noreferrer"&gt;https://pub.dev/packages/camera_pro&lt;/a&gt; and &lt;a href="https://github.com/sayed3li97/camera_pro" rel="noopener noreferrer"&gt;https://github.com/sayed3li97/camera_pro&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>ffi</category>
      <category>cpp</category>
    </item>
    <item>
      <title>The LLM Preamble Problem: How RLHF Made Your Model Too Polite to Ship</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Mon, 29 Jun 2026 17:50:10 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/the-llm-preamble-problem-how-rlhf-made-your-model-too-polite-to-ship-1c9l</link>
      <guid>https://dev.to/sayed_ali_alkamel/the-llm-preamble-problem-how-rlhf-made-your-model-too-polite-to-ship-1c9l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every instruction-tuned model has a preamble habit: it opens with "Certainly!", "Great question!", or "Of course!" before answering you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RLHF is the root cause.&lt;/strong&gt; Human raters rewarded warm, thorough responses during training. The model learned that politeness signals quality.&lt;/li&gt;
&lt;li&gt;Each preamble adds &lt;strong&gt;10 to 30 tokens of pure waste.&lt;/strong&gt; At 1 million interactions per month, that burns $200 in API budget before your users see a single useful word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four fixes exist.&lt;/strong&gt; System-prompt suppression, structured output (JSON mode), constrained decoding, and post-process stripping. Each fits a different situation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Every Instruction-Tuned LLM Has a Preamble Habit&lt;/li&gt;
&lt;li&gt;The RLHF Root Cause&lt;/li&gt;
&lt;li&gt;What the Preamble Actually Costs&lt;/li&gt;
&lt;li&gt;Four Fixes for the LLM Preamble, Ranked by Reliability&lt;/li&gt;
&lt;li&gt;The Skeptic's Objection&lt;/li&gt;
&lt;li&gt;What This Means For You&lt;/li&gt;
&lt;li&gt;Questions developers are actually asking about LLM preamble&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Somewhere around 3 AM in a data center, a human annotator reads two AI responses and picks the friendlier one. Not the faster one. Not the more accurate one. The one that says "Great question!" before answering. They do this tens of thousands of times. A reward model learns their preferences. And now, in every production application you ship, the first thing your LLM says to users is "Certainly, I'd be happy to help with that!" before it actually helps with anything.&lt;/p&gt;

&lt;p&gt;This is not a flaw your model's creator forgot to fix. It is the intended product of how instruction tuning works, amplified by how humans behave when asked to rate AI responses. The politeness is not a bug. It is a feature that made demos feel warmer and somehow survived all the way into your production logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Every Instruction-Tuned LLM Has a Preamble Habit
&lt;/h2&gt;

&lt;p&gt;A base language model, given "What is the capital of France?", completes the token stream. "Paris is the capital of..." An instruction-tuned model, trained to behave like a helpful assistant, answers differently: "Great question! The capital of France is Paris."&lt;/p&gt;

&lt;p&gt;That gap exists because of Reinforcement Learning from Human Feedback. RLHF takes a base model, runs it through a preference-optimization loop with human annotators, and fine-tunes it to maximize the reward model's approval score. Research cited in alignment literature (Singhal et al., 2023) found that &lt;strong&gt;response length is a significant latent optimization target&lt;/strong&gt; inside RLHF reward models. Responses that opened with a warm acknowledgment before the actual content scored consistently higher than responses that answered immediately, even when the core information was identical.&lt;/p&gt;

&lt;p&gt;The model did not learn to be genuine. It learned to perform helpfulness. "If I say 'Certainly!' before I answer, the preference model gives me gold stars."&lt;/p&gt;

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

&lt;p&gt;A March 2026 paper by Liu et al. quantified what researchers now call the &lt;strong&gt;alignment tax&lt;/strong&gt;: RLHF-aligned models show measurable response homogenization compared to their base versions. On TruthfulQA, 40% of questions produced semantically identical responses across multiple independent samples from the instruct-tuned model. The base model on the same benchmark: 0%.&lt;/p&gt;

&lt;p&gt;A three-stage ablation isolated the cause. Base model: 0% homogenization. After SFT only: 1.5%. After SFT plus DPO: 4%. The Direct Preference Optimization stage, not the supervised fine-tuning, drives the collapse in response diversity. This is where the preamble gets baked in. [INTERNAL LINK: earlier article on LLM tokenization and BPE]&lt;/p&gt;

&lt;p&gt;A more familiar way to state this: the model says "Of course!" not because it understands enthusiasm, but because responses beginning that way were repeatedly preferred during alignment training. It is not a conversational habit. It is a statistical artifact.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Training Stage&lt;/th&gt;
&lt;th&gt;Homogenization Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base model&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After SFT only&lt;/td&gt;
&lt;td&gt;1.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After SFT + DPO&lt;/td&gt;
&lt;td&gt;4.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Source: Liu, "The Alignment Tax: Response Homogenization in Aligned LLMs," 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Smaller instruction-tuned models amplify this effect. With fewer parameters to represent "helpfulness," they fall back on the most statistically common pattern more aggressively. I tested the same prompt across three models last month. Llama 3.2 3B led with preamble on 9 of 10 runs. Mistral 7B Instruct: 7 of 10. A frontier model: 3 of 10. Same prompt. Very different behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Preamble Actually Costs
&lt;/h2&gt;

&lt;p&gt;Each preamble adds 10 to 30 output tokens that carry no information. This feels trivial at the API call level. The arithmetic changes when you do it at production scale.&lt;/p&gt;

&lt;p&gt;At GPT-4o output pricing of $10 per million tokens (a legacy model as of 2026, but its price has held unchanged since OpenAI's October 2024 cut): 1 million API calls per month, 20 wasted tokens per call, equals 20 million tokens of pure "I'd be happy to help!" That is $200 per month. At 10 million calls per month: $2,000. Not company-ending numbers. But real, sustained waste on every response, month after month.&lt;/p&gt;

&lt;p&gt;To put that in physical terms: 20 million tokens at four characters per token is 80 megabytes of text. Every month, at 1M calls, your application writes the equivalent of an 80MB document that says "Certainly!" in several hundred thousand different ways. You pay for every character.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofw1ifykxat2dcnh9h4a.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofw1ifykxat2dcnh9h4a.png" alt=" " width="799" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Fixes for the LLM Preamble, Ranked by Reliability
&lt;/h2&gt;

&lt;p&gt;No single fix works universally. The most common one (system-prompt instruction) is also the least reliable at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix 1: System Prompt Suppression&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add an explicit negative instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System: Respond directly. Do not acknowledge the question with openers like 
"Great question", "Of course", "Certainly", or "I'd be happy to help". 
Begin immediately with the answer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works reliably on frontier models most of the time. It fails on small instruction-tuned models (below 7B parameters) and at higher temperatures. The preamble behavior sits deep enough in the weights that a single system prompt instruction does not always override it.&lt;/p&gt;

&lt;p&gt;Add a CI slop check to catch regressions when someone edits the system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# slop-check.sh&lt;/span&gt;
&lt;span class="nv"&gt;SLOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Certainly!|Of course!|Great question!|I'd be happy"&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qiE&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SLOP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL: preamble detected in LLM output"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix 2: Structured Output (JSON / Tool Mode)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For structured tasks, this is the most reliable fix available. When the model is constrained to valid JSON, the preamble cannot occur. "Certainly! {" is not valid JSON. The constraint kills the behavior at the token level.&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;from&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&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="nc"&gt;Anthropic&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-sonnet-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;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Extract the requested data. Return only valid JSON.&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Capital of France?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;tools&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;name&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;answer&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;description&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;Return the structured answer&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;input_schema&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;object&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;properties&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;string&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&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="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;tool_choice&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;type&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;auto&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every major provider now offers native structured output with schema guarantees. XGrammar, as of March 2026, is the default backend for vLLM, SGLang, and TensorRT-LLM, producing constrained output at under 40 microseconds of overhead per token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix 3: Constrained Decoding (Local Models)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are running a local model, Outlines and llama.cpp grammar constraints apply restrictions at the logit level before sampling. The token "Certainly" never enters the probability distribution. The library masks it out at generation time.&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;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;outlines&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outlines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mistralai/Mistral-7B-Instruct-v0.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;generator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outlines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the capital of France?&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "Paris" -- no preamble physically possible
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Flutter developers building on-device AI with quantized models (Phi-4-mini, Gemma 3, or Qwen 3), this path via llama.cpp or MediaPipe is your most reliable option. The preamble is prevented structurally, not instructed away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix 4: Post-Process Stripping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you cannot modify the model's behavior (third-party API, fixed vendor contract), strip the preamble from the output before returning it to users.&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;re&lt;/span&gt;

&lt;span class="n"&gt;PREAMBLE_PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^(Certainly!|Of course!|Sure!|Great question!|Absolutely!)\s*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^(I(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;d| would) be happy to help[^.]*\.\s*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^(Here is your[^:]*:\s*)&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;strip_preamble&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;span class="nb"&gt;str&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;str&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;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PREAMBLE_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&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;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches known patterns. It misses novel variations. Treat the pattern list as living documentation, not a complete solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technique Comparison&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Reliability&lt;/th&gt;
&lt;th&gt;Works on Prose&lt;/th&gt;
&lt;th&gt;Cloud API&lt;/th&gt;
&lt;th&gt;Local Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post-Process Stripping&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System Prompt Suppression&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON / Tool Mode&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constrained Decoding&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60mq3rkqz34v679s0jzq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60mq3rkqz34v679s0jzq.png" alt=" " width="799" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skeptic's Objection
&lt;/h2&gt;

&lt;p&gt;The fair criticism here is: "This is just prompt engineering. Write a better system prompt."&lt;/p&gt;

&lt;p&gt;I tested this objection on a real project: integrating Phi-3.5-mini into a banking support flow at Oman Housing Bank. The system prompt had three separate instructions to skip preamble. The model complied on roughly 70% of calls. On the remaining 30%, it produced preambles ranging from 12 to 41 tokens. At the volumes we were processing, that failure rate was not a prompting problem. It was a latency and cost problem requiring structural intervention.&lt;/p&gt;

&lt;p&gt;The deeper issue is that preamble behavior is not a surface-level instruction-following failure. It is encoded in the model's output distribution at the weight level, as the alignment tax research confirmed. Telling the model "skip the greeting" is something it can forget. Constraining the token distribution to valid JSON is something it physically cannot disobey. One is a request. The other is an architecture decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means For You
&lt;/h2&gt;

&lt;p&gt;You are building a chatbot with a frontier API. Start with system-prompt suppression and add the CI slop check. That combination covers most cases and takes 30 minutes to implement.&lt;/p&gt;

&lt;p&gt;You are doing structured tasks: classification, extraction, summarization into a defined schema. Use JSON mode or tool-use mode. The preamble problem disappears entirely. Do not spend engineering budget on any other fix for this use case.&lt;/p&gt;

&lt;p&gt;You are running a local model for a privacy-first or on-device deployment. Evaluate constrained decoding via Outlines or llama.cpp grammar constraints. The upfront complexity pays back at volume, and it is the only fix with a formal correctness guarantee.&lt;/p&gt;

&lt;p&gt;You are integrating a third-party API you do not control. Post-process stripping is your only lever. Build the pattern library, add the slop check to CI, and accept that you will be updating the list. [INTERNAL LINK: article on agentic CI/CD pipelines]&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions developers are actually asking about LLM preamble
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why do LLMs say "Certainly!" before answering?
&lt;/h3&gt;

&lt;p&gt;This behavior comes from RLHF: the reinforcement learning process that aligns base models with human preferences. Human annotators consistently scored responses with warm, acknowledging openers as higher quality than responses that answered immediately. The reward model learned this signal and the policy model maximized it. Research by Singhal et al. (2023) confirmed that response length optimization is a significant hidden factor in RLHF reward modeling, and preamble is a direct output of that optimization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the preamble problem affect all models equally?
&lt;/h3&gt;

&lt;p&gt;No. Smaller instruction-tuned models (under 7B parameters) exhibit preamble behavior more frequently because they have fewer representational paths to express "helpfulness" and fall back on the most statistically common pattern. A 2026 alignment study found that the effect varies significantly by model family and training recipe, from under 2% homogenization in some Mistral variants to over 28% in Qwen3-14B instruct. The size of the effect depends on how aggressively the preference optimization stage was applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the most reliable way to suppress LLM preamble?
&lt;/h3&gt;

&lt;p&gt;Structured output (JSON mode or tool-use mode) is the most reliable suppression technique for structured tasks. When the model must produce valid JSON, "Certainly! {" is syntactically impossible. For local model deployments, constrained decoding via XGrammar or Outlines applies the constraint at the logit level before sampling, making preamble generation physically impossible. System-prompt instructions are the least reliable option because the behavior is encoded at the weight level, not the instruction-following level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I detect LLM preamble in a CI/CD pipeline?
&lt;/h3&gt;

&lt;p&gt;Yes, with a simple bash script running grep against known preamble patterns during your test suite or as a deployment gate. Check for "Certainly!", "Of course!", "Great question!", "I'd be happy to", and "I'll help you with that." This approach catches the most common variants but requires ongoing maintenance as models generate new preamble patterns. Treat it as a signal, not an exhaustive filter, and combine it with other suppression techniques for production reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the LLM preamble cost real money at scale?
&lt;/h3&gt;

&lt;p&gt;Yes. At GPT-4o output pricing of $10 per million tokens (confirmed unchanged through April 2026), 1 million API calls per month with 20 wasted tokens each burns $200 in preamble tokens. At 10 million calls, that is $2,000 per month. The cost scales linearly with call volume and proportionally with the model's output token pricing. For premium models with higher per-token costs, the number rises faster. It is not company-ending. It is real, sustained, and entirely avoidable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Longer View
&lt;/h2&gt;

&lt;p&gt;Every generation of developers has had to compensate for some mismatch between what a system was optimized for and what they actually needed it to do.&lt;/p&gt;

&lt;p&gt;The LLM preamble problem is that mismatch for 2025 and 2026. RLHF made models dramatically more usable for non-technical users. It also gave every model a slightly performative quality: the tendency to appear helpful in the first token, before being helpful in the subsequent ones. That gap between performance and function is not something you can always prompt your way around. It is a property of how the system was trained. The alignment tax paper was not describing a bug report. It was describing the cost of making a language model agreeable to human raters.&lt;/p&gt;

&lt;p&gt;The developers who ship reliable LLM-powered products in this environment are the ones who understand the training's side effects well enough to design around them. Not that the model is broken. That it is doing exactly what it was trained to do, and that your application needs to account for what that training left behind.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Liu, M. "The Alignment Tax: Response Homogenization in Aligned LLMs and Its Implications for Uncertainty Estimation." arXiv, March 2026. &lt;a href="https://arxiv.org/pdf/2603.24124" rel="noopener noreferrer"&gt;https://arxiv.org/pdf/2603.24124&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Singhal, P. et al. Cited in: Zeng et al. "Verbosity is Not Veracity: Demystify Verbosity Compensation Behavior of Large Language Models." arXiv, 2024. &lt;a href="https://arxiv.org/pdf/2411.07858" rel="noopener noreferrer"&gt;https://arxiv.org/pdf/2411.07858&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;West, A. "How to Fix That Robotic AI Tone in Your LLM-Powered Features." DEV Community, April 2026. &lt;a href="https://dev.to/alanwest/how-to-fix-that-robotic-ai-tone-in-your-llm-powered-features-4h5e"&gt;https://dev.to/alanwest/how-to-fix-that-robotic-ai-tone-in-your-llm-powered-features-4h5e&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pristren. "How to Reduce LLM Output Tokens by 40-60%." May 2026. &lt;a href="https://pristren.com/blog/reduce-output-tokens-guide/" rel="noopener noreferrer"&gt;https://pristren.com/blog/reduce-output-tokens-guide/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Vuyyuru, A. "9 Practical Tips to Stop Burning Tokens on LLMs." Substack, May 2026. &lt;a href="https://abhijayvuyyuru.substack.com/p/9-practical-tips-to-stop-burning" rel="noopener noreferrer"&gt;https://abhijayvuyyuru.substack.com/p/9-practical-tips-to-stop-burning&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pockit Tools. "LLM Structured Output in 2026: Stop Parsing JSON with Regex." DEV Community, February 2026. &lt;a href="https://dev.to/pockit_tools/llm-structured-output-in-2026-stop-parsing-json-with-regex-and-do-it-right-34pk"&gt;https://dev.to/pockit_tools/llm-structured-output-in-2026-stop-parsing-json-with-regex-and-do-it-right-34pk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nambiar, B. "Beyond Free-Form Text: How Constrained Decoding is Reshaping Structured Generation in LLMs." Medium, September 2025. &lt;a href="https://medium.com/@brijeshrn/beyond-free-form-text-how-constrained-decoding-is-reshaping-structured-generation-in-llms-5f7a38bef259" rel="noopener noreferrer"&gt;https://medium.com/@brijeshrn/beyond-free-form-text-how-constrained-decoding-is-reshaping-structured-generation-in-llms-5f7a38bef259&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Let's Data Science. "How Structured Outputs and Constrained Decoding Work." March 2026. &lt;a href="https://letsdatascience.com/blog/structured-outputs-making-llms-return-reliable-json" rel="noopener noreferrer"&gt;https://letsdatascience.com/blog/structured-outputs-making-llms-return-reliable-json&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hecatus Research. "Less is More for LLMs? A Critique of Prompt-Based Compression." Medium, May 2026. &lt;a href="https://medium.com/hecatus-research/less-is-more-for-llms-a-critique-of-prompt-based-compression-910978d8bad4" rel="noopener noreferrer"&gt;https://medium.com/hecatus-research/less-is-more-for-llms-a-critique-of-prompt-based-compression-910978d8bad4&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;BuildML. "If an LLM keeps producing excessively verbose answers, how would you correct it?" Substack, December 2025. &lt;a href="https://substack.com/@buildml/note/c-190308122" rel="noopener noreferrer"&gt;https://substack.com/@buildml/note/c-190308122&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Sayed Ali Alkamel is a Google Developer Expert in Dart and Flutter, co-founder of Flutter MENA, and Manager of Digital Application Platforms at Oman Housing Bank. He has spoken at tech events across 22+ countries and shipped apps with 2.5M+ downloads. He writes about Flutter, AI, and the developer experience at dev.to/sayed_ali_alkamel.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GGUF, Explained: The File Format That Put LLMs on Your Laptop</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Wed, 24 Jun 2026 15:37:26 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/gguf-explained-the-file-format-that-put-llms-on-your-laptop-12lh</link>
      <guid>https://dev.to/sayed_ali_alkamel/gguf-explained-the-file-format-that-put-llms-on-your-laptop-12lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GGUF&lt;/strong&gt; is a single binary file that packs a model's weights, tokenizer, and architecture metadata together, so a tool can open it and run with no side files.&lt;/li&gt;
&lt;li&gt;The name on the file, like &lt;strong&gt;Q4_K_M&lt;/strong&gt; or &lt;strong&gt;Q8_0&lt;/strong&gt;, encodes a &lt;strong&gt;quantization&lt;/strong&gt; choice: how many bits each weight gets, which decides size, speed, and quality.&lt;/li&gt;
&lt;li&gt;For most consumer hardware, &lt;strong&gt;Q4_K_M&lt;/strong&gt; is the right default. It cuts an 8B model to about 5 GB for a 1 to 3 percent quality cost.&lt;/li&gt;
&lt;li&gt;GGUF is the reason serious models run on hardware you already own, with your data staying on the device.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why does a model need a special file format at all?&lt;/li&gt;
&lt;li&gt;What is actually inside a GGUF file?&lt;/li&gt;
&lt;li&gt;What do the quantization names like Q4_K_M actually mean?&lt;/li&gt;
&lt;li&gt;How much quality do you actually lose?&lt;/li&gt;
&lt;li&gt;The fair criticism: isn't 4-bit just lossy compression that makes the model dumber?&lt;/li&gt;
&lt;li&gt;How do you convert a model to GGUF yourself?&lt;/li&gt;
&lt;li&gt;How to pick a quant for your hardware&lt;/li&gt;
&lt;li&gt;Questions developers are actually asking about GGUF&lt;/li&gt;
&lt;li&gt;The quiet format that changed who gets to run AI&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;About the author&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Forty years ago, a single gigabyte of computer memory cost more than a house and filled a cabinet. The phone in your pocket carries six to twelve times that, and it can run a language model trained on a meaningful slice of the open internet. The thing that carried that model from a research cluster into your hand was not a new chip. It was a file format.&lt;/p&gt;

&lt;p&gt;If you have ever downloaded a model to run locally, you have already met it. The file ended in &lt;code&gt;.gguf&lt;/code&gt;, and a tool like Ollama or LM Studio opened it without asking you for a config folder, a tokenizer directory, or a Python environment full of pinned versions. That quiet competence hides a lot of engineering.&lt;/p&gt;

&lt;p&gt;GGUF is worth understanding because the filename is a decision. Q4_K_M, Q5_K_M, Q8_0: each one is a different answer to a single question, which is how much quality you are willing to trade for a model that actually fits on your machine. Most people pick by guessing. You do not have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a model need a special file format at all?
&lt;/h2&gt;

&lt;p&gt;A trained model is, underneath everything, a very large pile of numbers. Billions of them. Formats like PyTorch's &lt;code&gt;.pth&lt;/code&gt; and Hugging Face's &lt;code&gt;.safetensors&lt;/code&gt; store mostly those numbers and little else. To turn that pile into something you can actually run, you need more: a &lt;code&gt;config.json&lt;/code&gt; that describes the architecture, one or more tokenizer files, a generation config, and sometimes custom Python that defines how the model is wired. You keep all of it together, you match the versions, and you have a framework installed to read it.&lt;/p&gt;

&lt;p&gt;Quantized models make this harder, not easier. When you compress weights from 16 bits down to 4, you cannot just store smaller numbers. You also have to store the scaling factors and offsets needed to reconstruct each block, and the generic formats were never designed to carry that bookkeeping cleanly. Standard model saving methods fall short with quantized models, which need to store low-bit weights alongside their scaling factors and zero-points. GGUF was the practical answer to that gap.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqn1nxcodnmh8ywrf7eq8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqn1nxcodnmh8ywrf7eq8.png" alt="Exhibit 1: a diagram showing many separate files, including safetensors weights, config.json, tokenizer files, and architecture code on the left, converted into a single .gguf file on the right containing a header, metadata, and tensor data sections." width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually inside a GGUF file?
&lt;/h2&gt;

&lt;p&gt;A GGUF file introduces itself before you ask it anything. Open it and the first thing you find is structure, not just data. The format is a binary file with three main parts: a header, a metadata section, and the tensor data. The header is small bookkeeping. The metadata is where the magic lives: the architecture, the hyperparameters, the tokenizer, and the quantization type all sit there as typed key value pairs. The tensor data is the weights themselves, packed and aligned so they can be read straight off disk.&lt;/p&gt;

&lt;p&gt;That self-describing design is the whole point. The runtime inspects the metadata, understands the architecture, loads the tokenizer, and maps the tensors without relying on a separate config.json or tokenizer folder. The weights are laid out for memory mapping, which means the model loads fast because the operating system can page it in instead of copying gigabytes around first.&lt;/p&gt;

&lt;p&gt;GGUF grew out of an older format. It was introduced as part of the llama.cpp and GGML ecosystem in August 2023, and it is now the dominant format for distributing quantized local LLMs on Hugging Face. If you are downloading a model to run today, you are almost certainly downloading a &lt;code&gt;.gguf&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do the quantization names like Q4_K_M actually mean?
&lt;/h2&gt;

&lt;p&gt;This is where most people get lost, so let me save you the hour I lost staring at a Hugging Face file list. The naming scheme is genuinely user-hostile. Nothing about &lt;code&gt;Q4_K_M&lt;/code&gt; tells a newcomer that the M means medium, or that the K is the good kind. Here is how to read it.&lt;/p&gt;

&lt;p&gt;The leading number is roughly the bits per weight. Q8 is eight bits, Q4 is four. The full picture is more interesting than that, because there are three families.&lt;/p&gt;

&lt;p&gt;The legacy family is &lt;code&gt;Q4_0&lt;/code&gt;, &lt;code&gt;Q4_1&lt;/code&gt;, &lt;code&gt;Q5_0&lt;/code&gt;, &lt;code&gt;Q5_1&lt;/code&gt;, and &lt;code&gt;Q8_0&lt;/code&gt;. These use classic per-block linear quantization, where a block stores n-bit codes and either one scale (the symmetric "_0" variants) or a scale plus an offset (the "_1" variants), decoded with a single transform per block. They are simple and fast to decode. Their weakness shows at low bit widths, where one flat map per block cannot capture skewed weight distributions well.&lt;/p&gt;

&lt;p&gt;The K-quants are the modern default: &lt;code&gt;Q3_K&lt;/code&gt;, &lt;code&gt;Q4_K&lt;/code&gt;, &lt;code&gt;Q5_K&lt;/code&gt;, &lt;code&gt;Q6_K&lt;/code&gt;, each with &lt;code&gt;_S&lt;/code&gt;, &lt;code&gt;_M&lt;/code&gt;, and &lt;code&gt;_L&lt;/code&gt; variants. The K stands for a method the llama.cpp community built, and it is smarter about where it spends its bits. K-quants perform importance-weighted bit allocation: attention and output projection tensors, which disproportionately affect quality, keep higher precision, often 6 bits, while less critical feedforward layers are quantized more aggressively. The suffix is the mix. Q4_K_M (Medium) keeps more sensitive layers at 6-bit, while Q4_K_S (Small) pushes more weights to 4-bit to save a few hundred megabytes on an 8B model.&lt;/p&gt;

&lt;p&gt;The newest family is the I-quants, written &lt;code&gt;IQ2&lt;/code&gt;, &lt;code&gt;IQ3&lt;/code&gt;, &lt;code&gt;IQ4&lt;/code&gt;, with names like &lt;code&gt;IQ4_XS&lt;/code&gt;. The IQ family reconstructs weights using a super-block scale and an importance matrix, aimed at preserving quality at low bitrates. They squeeze more model into less space, at the cost of being more sensitive to how the quant was produced.&lt;/p&gt;

&lt;p&gt;Here is a detail worth internalizing, because it changes how you think about all of this. Text generation is memory-bandwidth-bound. The GPU spends most of its time reading weights from memory, not doing multiply-adds, so a bigger quantization means more bytes to read per token and slower output. A smaller file is not just cheaper to store. It is literally fewer bytes to haul for every word the model writes. That is why a Q4 model often types faster than its Q8 twin. It does not think faster, it reads less.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpb8drv2q7yh7dz28fle.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpb8drv2q7yh7dz28fle.png" alt="Exhibit 2: a horizontal bar chart of file size on disk for Llama-3.1-8B across quantization levels, from FP16 at 16 GB down through Q8_0, Q6_K, Q5_K_M, Q4_K_M at 4.9 GB, Q3_K_M, and Q2_K at 3.2 GB, with Q4_K_M highlighted as the most downloaded default." width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hard numbers, for a current 8B model, look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quant&lt;/th&gt;
&lt;th&gt;Size (Llama-3.1-8B)&lt;/th&gt;
&lt;th&gt;Quality vs FP16&lt;/th&gt;
&lt;th&gt;Reach for it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;~16 GB&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;You are benchmarking or fine-tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;~8.5 GB&lt;/td&gt;
&lt;td&gt;under 0.5% loss&lt;/td&gt;
&lt;td&gt;Code, math, agents, and you have the VRAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q6_K&lt;/td&gt;
&lt;td&gt;~6.6 GB&lt;/td&gt;
&lt;td&gt;~1% loss&lt;/td&gt;
&lt;td&gt;A near-lossless step below Q8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q5_K_M&lt;/td&gt;
&lt;td&gt;~5.7 GB&lt;/td&gt;
&lt;td&gt;~1% loss&lt;/td&gt;
&lt;td&gt;You have spare memory over Q4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Q4_K_M&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~4.9 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 to 3% loss&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The default for most people&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4_K_S&lt;/td&gt;
&lt;td&gt;~4.7 GB&lt;/td&gt;
&lt;td&gt;slightly worse&lt;/td&gt;
&lt;td&gt;Short by a few hundred MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q3_K_M&lt;/td&gt;
&lt;td&gt;~4.0 GB&lt;/td&gt;
&lt;td&gt;noticeably worse&lt;/td&gt;
&lt;td&gt;Memory is very tight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q2_K&lt;/td&gt;
&lt;td&gt;~3.2 GB&lt;/td&gt;
&lt;td&gt;severe&lt;/td&gt;
&lt;td&gt;Last resort only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;File sizes are bartowski's Meta-Llama-3.1-8B-Instruct GGUFs on Hugging Face. Quality figures are approximate and vary by model and benchmark.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How much quality do you actually lose?
&lt;/h2&gt;

&lt;p&gt;For a modern 8B model, moving from full precision to Q4_K_M costs roughly 1 to 3 percent on MMLU, which is imperceptible in everyday chat and writing. Q8_0 loses under half a percent. The damage only becomes obvious in the small quants, Q2 and Q3, where reasoning visibly falls apart. That is the short version, and for most readers it is the whole answer.&lt;/p&gt;

&lt;p&gt;The longer version comes from people who measured it carefully rather than vibing it. A 2026 study titled "Which Quantization Should I Use?" ran a unified evaluation of llama.cpp formats on Llama-3.1-8B-Instruct and made a point worth repeating: these quantization schemes are largely community-driven, with new formats, effective bit estimates, and recommended usage patterns emerging through GitHub issues, pull requests, and informal benchmark reports. In other words, the folklore in those Hugging Face comment threads is doing real work, and it is now being checked against actual downstream tasks.&lt;/p&gt;

&lt;p&gt;One concrete number anchors the speed side of the trade. The llama.cpp project publishes benchmark figures for Llama-3.1-8B, and on those numbers Q8_0 generates tokens about 29 percent more slowly than Q4_K_M. That is the bandwidth tax made visible. You pay it in tokens per second for the fidelity you buy.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlbkhu7tdd33hkhj76sh.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlbkhu7tdd33hkhj76sh.png" alt="Exhibit 3: a line chart plotting approximate quality retained versus file size for Llama-3.1-8B class quants, showing a sharp knee at Q4_K_M around 97 percent quality and a long flat tail through Q5, Q6, Q8, and FP16, labeled as a diminishing returns zone." width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The shape of that curve is the thing to remember. Below Q4 the quality drops off a cliff. Above Q4 the line goes nearly flat, so each extra gigabyte you spend buys you almost nothing you can feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fair criticism: isn't 4-bit just lossy compression that makes the model dumber?
&lt;/h2&gt;

&lt;p&gt;Yes, it is lossy. Pretending otherwise would be dishonest. The honest answer is that it depends entirely on where you point the model.&lt;/p&gt;

&lt;p&gt;For chat, summarization, and drafting, the 1 to 3 percent loss at Q4_K_M is invisible in practice, and I have shipped on-device features built on exactly that trade. For code generation, math, and multi-step agent workflows, the calculus changes, because small errors compound across steps and a single wrong token early can derail a whole chain. Q8_0 loses under 0.5 percent versus FP16 while Q4_K_M loses 1 to 3 percent, a gap that is imperceptible in everyday use but can matter on precise numerical reasoning. So the criticism is fair, and the response is to match the quant to the job rather than to argue that 4-bit is free.&lt;/p&gt;

&lt;p&gt;Here is the part the enthusiast threads tend to skip. Requantizing a file that was already quantized, or running an aggressive IQ2 on a small model, can produce output that is confidently wrong in ways an average benchmark score will not catch. The number looks fine. The behavior is not.&lt;/p&gt;

&lt;p&gt;There is a second limit that has nothing to do with bits. Self-describing does not mean future-proof. A GGUF file is not universally compatible forever, because the runtime still has to support the model architecture and tensor types used in the file. A brand-new architecture can land on Hugging Face and your favorite tool simply cannot read it until the maintainers add support.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you convert a model to GGUF yourself?
&lt;/h2&gt;

&lt;p&gt;The path is two steps: convert to a high-precision GGUF, then quantize it down. The llama.cpp repository ships both tools, and the &lt;a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md" rel="noopener noreferrer"&gt;official quantize guide&lt;/a&gt; documents the full set of options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Convert a Hugging Face model to a high-precision GGUF (bf16)&lt;/span&gt;
python convert_hf_to_gguf.py ./Llama-3.1-8B-Instruct &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outtype&lt;/span&gt; bf16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outfile&lt;/span&gt; llama-3.1-8b-bf16.gguf

&lt;span class="c"&gt;# 2) Quantize that file down to Q4_K_M&lt;/span&gt;
./llama-quantize llama-3.1-8b-bf16.gguf &lt;span class="se"&gt;\&lt;/span&gt;
  llama-3.1-8b-Q4_K_M.gguf Q4_K_M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have the file, running it from Python takes a few lines with &lt;code&gt;llama-cpp-python&lt;/code&gt;. The &lt;code&gt;n_gpu_layers&lt;/code&gt; argument is the partial-offload lever: push as many layers onto the GPU as your VRAM allows, and the rest run on the CPU.&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;from&lt;/span&gt; &lt;span class="n"&gt;llama_cpp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Llama&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Llama&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama-3.1-8b-Q4_K_M.gguf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n_gpu_layers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# offload 20 layers to the GPU, run the rest on CPU
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain GGUF in one sentence:&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;64&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;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choices&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you would rather skip the local setup entirely, the GGUF-my-repo space on Hugging Face builds quantized files for you and stays in sync with llama.cpp. I leaned on that more than once before I had the toolchain set up properly. Worth mentioning, the first time I converted a model myself, the conversion script choked on a tokenizer it did not recognize and the error told me almost nothing useful, so do not be surprised if step one needs a second attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to pick a quant for your hardware
&lt;/h2&gt;

&lt;p&gt;Stop choosing by reputation and choose by the memory in front of you. The decision is mostly mechanical once you see it framed as hardware.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyyozy6okqxwal6skf53.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyyozy6okqxwal6skf53.png" alt="Exhibit 4: a comparison of memory required to run an 8B model, showing FP16 needing a 16 GB or larger dedicated GPU versus Q4_K_M needing about 5 GB and running on a 6 GB GPU, a laptop CPU, a Raspberry Pi, or a phone." width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A practical guide, from least to most headroom:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a laptop with no dedicated GPU, or with 6 to 8 GB of VRAM, use Q4_K_M. It is the most-downloaded quant for a reason, and it gives you the most model per gigabyte.&lt;/li&gt;
&lt;li&gt;With 12 to 16 GB of VRAM, step up to Q5_K_M, or Q6_K if it fits, for a small bump in fidelity.&lt;/li&gt;
&lt;li&gt;With 24 GB or more, and when you care about code, math, or tool calling, run Q8_0. The sub-half-percent loss is cheap insurance for work where errors compound.&lt;/li&gt;
&lt;li&gt;Short by only a few hundred megabytes? Try Q4_K_S or IQ4_XS before you abandon the model. IQ4_XS reaches near-identical perplexity at a smaller size than Q4_K_M, useful when you need to squeeze one more gigabyte out of a tight budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the very large models, the math gets unforgiving. A 70B model at Q4_K_M lands around 42.5 GB and fits across two 24 GB consumer GPUs with room for the cache, while Q5_K_M overflows that by about 2 GB before you add any context. At that scale Q4_K_M is not a preference. It is the ceiling.&lt;/p&gt;

&lt;p&gt;If your target is a phone, pair a small model in the 1B to 4B range with Q4_K_M and keep the context window modest. I have watched a Q5 build of an 8B model run out of memory on a mid-range phone while the Q4_K_M of the same model ran without complaint. The filename was the entire difference. If you want to go deeper on running models fully on-device, I covered the Flutter side of this in &lt;a href="https://dev.to[INTERNAL%20LINK]"&gt;a separate piece&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions developers are actually asking about GGUF
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does GGUF stand for?
&lt;/h3&gt;

&lt;p&gt;GGUF is the single-file binary format for storing large language models, created for the llama.cpp project as the successor to the older GGML format. The GG comes from Georgi Gerganov, the developer behind both. The acronym is most often expanded as GPT-Generated Unified Format, though you will also see GGML Universal File, and the official specification does not actually spell it out. Whatever you call it, a GGUF file packs the model weights, the tokenizer, and the architecture metadata into one file you can download and run with a compatible tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between GGUF and safetensors?
&lt;/h3&gt;

&lt;p&gt;safetensors is a safe, fast container for raw model weights, used widely for full-precision models and for training and fine-tuning. GGUF adds the architecture metadata and tokenizer inside the file and is built around quantization for efficient inference, including on CPU. Use safetensors in training and research pipelines, and use GGUF for running quantized models locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which GGUF quantization should I use?
&lt;/h3&gt;

&lt;p&gt;For most people on consumer hardware, Q4_K_M is the right default. It cuts an 8B model to about 5 GB with a 1 to 3 percent quality loss that is imperceptible in chat and writing. Move up to Q5_K_M or Q8_0 if you have spare memory and need higher fidelity for code or math, and drop to Q4_K_S or IQ4_XS only if you are short a few hundred megabytes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can GGUF models run on a GPU or only on the CPU?
&lt;/h3&gt;

&lt;p&gt;GGUF runs on CPU, GPU, or both. The llama.cpp engine supports full GPU execution, CPU-only inference using AVX and ARM NEON instructions, and partial offloading where some layers run on the GPU and the rest on the CPU. The partial-offload path is what lets a model that does not fully fit in VRAM still run with GPU acceleration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Q4_K_M good enough, or do I need Q8_0?
&lt;/h3&gt;

&lt;p&gt;Q4_K_M is good enough for chat, writing, and summarization, where its 1 to 3 percent quality loss is invisible. Choose Q8_0 when you need maximum fidelity for code generation, math, or multi-step agent workflows, where small errors accumulate. Q8_0 loses under half a percent versus full precision, but the file runs about 1.7 times the size of Q4_K_M and generates tokens roughly 29 percent slower on llama.cpp's published Llama-3.1-8B benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I convert a model to GGUF?
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;convert_hf_to_gguf.py&lt;/code&gt; script from llama.cpp to turn a Hugging Face model into a high-precision GGUF, then run &lt;code&gt;llama-quantize&lt;/code&gt; to compress it to a level such as Q4_K_M. If you want no local setup at all, the GGUF-my-repo space on Hugging Face builds quantized GGUF files for you and stays in sync with the llama.cpp project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quiet format that changed who gets to run AI
&lt;/h2&gt;

&lt;p&gt;For a couple of years, the working assumption was that capable models live in someone else's data center, reachable only through an API key and a credit card. That assumption is breaking, and a file format is part of why.&lt;/p&gt;

&lt;p&gt;It is an unglamorous thing to credit. Formats do not trend. Yet formats are exactly where capability either spreads or stays locked behind a counter. PDF made documents portable across machines that agreed on nothing else. MP3 made an entire library fit in a pocket. GGUF is doing the same thing to trained intelligence: moving it onto hardware people already own, with their data staying on the device instead of crossing a network.&lt;/p&gt;

&lt;p&gt;Look at the arc and the direction is clear. The next decade of on-device AI is being built on a file you can copy to a USB stick and run on a phone. Knowing what is inside that file, and what the name on it quietly costs you in quality and speed, has stopped being a niche skill for a few enthusiasts. It is becoming part of the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;GGUF specification, ggml-org. &lt;a href="https://github.com/ggml-org/ggml/blob/master/docs/gguf.md" rel="noopener noreferrer"&gt;https://github.com/ggml-org/ggml/blob/master/docs/gguf.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;llama.cpp quantize tool README, ggml-org. &lt;a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md" rel="noopener noreferrer"&gt;https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;llama.cpp, "Difference in different quantization methods," Discussion #2094. &lt;a href="https://github.com/ggml-org/llama.cpp/discussions/2094" rel="noopener noreferrer"&gt;https://github.com/ggml-org/llama.cpp/discussions/2094&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;"Which Quantization Should I Use? A Unified Evaluation of llama.cpp Quantization on Llama-3.1-8B-Instruct," arXiv, 2026. &lt;a href="https://arxiv.org/html/2601.14277v1" rel="noopener noreferrer"&gt;https://arxiv.org/html/2601.14277v1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DataCamp, "GGUF Format: A Complete Guide to Local LLM Inference." &lt;a href="https://www.datacamp.com/tutorial/gguf-format-a-complete-guide" rel="noopener noreferrer"&gt;https://www.datacamp.com/tutorial/gguf-format-a-complete-guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;APXML, "LLM GGUF Guide: File Format, Structure, and How It Works." &lt;a href="https://apxml.com/posts/gguf-explained-llm-file-format" rel="noopener noreferrer"&gt;https://apxml.com/posts/gguf-explained-llm-file-format&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SitePoint, "Quantization Explained: Q4_K_M vs AWQ vs FP16 for Local LLMs." &lt;a href="https://www.sitepoint.com/quantization-q4km-vs-awq-fp16-local-llms/" rel="noopener noreferrer"&gt;https://www.sitepoint.com/quantization-q4km-vs-awq-fp16-local-llms/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The Kaitchup, "Choosing a GGUF Model: K-Quants, IQ Variants, and Legacy Formats." &lt;a href="https://kaitchup.substack.com/p/choosing-a-gguf-model-k-quants-i" rel="noopener noreferrer"&gt;https://kaitchup.substack.com/p/choosing-a-gguf-model-k-quants-i&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;"Q4 vs Q5 vs Q6 vs Q8 Quantization: Real Quality Loss Numbers for Local LLMs." &lt;a href="https://runaihome.com/blog/quantization-q4-q5-q6-q8-quality-loss-2026/" rel="noopener noreferrer"&gt;https://runaihome.com/blog/quantization-q4-q5-q6-q8-quality-loss-2026/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Michael Brenndoerfer, "GGUF Format: Efficient Storage and Inference for Quantized LLMs." &lt;a href="https://mbrenndoerfer.com/writing/gguf-format-quantized-llm-storage-inference" rel="noopener noreferrer"&gt;https://mbrenndoerfer.com/writing/gguf-format-quantized-llm-storage-inference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hugging Face, "GGUF" documentation. &lt;a href="https://huggingface.co/docs/hub/en/gguf" rel="noopener noreferrer"&gt;https://huggingface.co/docs/hub/en/gguf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;llama.cpp repository, ggml-org. &lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;https://github.com/ggml-org/llama.cpp&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  About the author
&lt;/h2&gt;

&lt;p&gt;Sayed Ali Alkamel is a Google Developer Expert in Dart and Flutter, Founder of Flutter MENA, and Manager of Digital Application Platforms at Oman Housing Bank. He has spoken at tech events across 22+ countries and shipped apps with 2.5M+ downloads. He writes about Flutter, AI, and the developer experience at dev.to/sayed_ali_alkamel.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI Gateways: A Senior Engineer's Honest Take</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:37:19 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/ai-gateways-a-senior-engineers-honest-take-1fn7</link>
      <guid>https://dev.to/sayed_ali_alkamel/ai-gateways-a-senior-engineers-honest-take-1fn7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;AI gateway&lt;/strong&gt; is a reverse proxy between your apps and your LLM providers. It gives you one endpoint, &lt;strong&gt;token-level cost control&lt;/strong&gt;, &lt;strong&gt;semantic caching&lt;/strong&gt;, model &lt;strong&gt;fallbacks&lt;/strong&gt;, &lt;strong&gt;guardrails&lt;/strong&gt;, and &lt;strong&gt;observability&lt;/strong&gt;, so none of that leaks into application code.&lt;/li&gt;
&lt;li&gt;It is not the same thing as an &lt;strong&gt;API gateway&lt;/strong&gt;. API gateways meter by request, cache by exact URL, and treat the request body as opaque. AI gateways meter by &lt;strong&gt;tokens&lt;/strong&gt;, cache by &lt;strong&gt;meaning&lt;/strong&gt;, and read the payload.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;options&lt;/strong&gt; split into three camps: open-source self-hosted (LiteLLM, Bifrost), managed and aggregator (Cloudflare, Vercel, OpenRouter), and enterprise control planes (Portkey, TrueFoundry, Kong).&lt;/li&gt;
&lt;li&gt;Add one once you call &lt;strong&gt;more than one provider&lt;/strong&gt; or your &lt;strong&gt;token bill&lt;/strong&gt; grows enough that budgets and caching matter. Adding it on day one of a single-provider prototype is a common mistake.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What developers are actually fighting when AI hits production&lt;/li&gt;
&lt;li&gt;What is an AI gateway, exactly?&lt;/li&gt;
&lt;li&gt;AI gateway vs API gateway: what actually changes&lt;/li&gt;
&lt;li&gt;The core features worth paying for in an AI gateway&lt;/li&gt;
&lt;li&gt;What AI gateway options exist in 2026?&lt;/li&gt;
&lt;li&gt;We already have an API gateway. Why add another hop?&lt;/li&gt;
&lt;li&gt;What this means for your stack&lt;/li&gt;
&lt;li&gt;Questions developers are actually asking about AI gateways&lt;/li&gt;
&lt;li&gt;The layer that learned to think&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every system that survives long enough grows a border. A cell membrane decides what crosses and what stays out. A customs desk decides what enters a country and what it costs to bring. Software grew its own version about twenty years ago and called it the API gateway: a quiet layer at the edge, checking credentials, counting requests, then waving traffic through and forgetting about it.&lt;/p&gt;

&lt;p&gt;That border was built on one assumption. The thing on the other side is a normal backend. It answers in milliseconds, it costs the same whether you ask it once or ask it cleverly, and it returns the same response to the same request every single time. For two decades that assumption held, and the gateway could afford to be dumb on purpose.&lt;/p&gt;

&lt;p&gt;Then we wired our applications to large language models, and every part of that assumption broke at once. The backend now charges by the word. It can take ten seconds to answer. It streams its reply one token at a time, and it will cheerfully hand you two different answers to the same question. The old border cannot see any of this. That gap is where the AI gateway lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What developers are actually fighting when AI hits production
&lt;/h2&gt;

&lt;p&gt;The first LLM feature in any codebase looks innocent. One provider, one API key, a few calls wrapped in a helper function. It works in the demo and it ships.&lt;/p&gt;

&lt;p&gt;The trouble starts at the second provider. Maybe one model is better at reasoning and another is cheaper for bulk summarization. Maybe your primary provider has an outage during a customer demo and you want a fallback that does not require a deploy. The instant you have two, the cross-cutting concerns that were hiding in that helper function start spreading: auth for each provider, retry logic, a place to track spend, somewhere to log what went out and what came back. Copy that helper into three services and you no longer have a pattern. You have a liability.&lt;/p&gt;

&lt;p&gt;The money makes it urgent. Menlo Ventures put enterprise spend on foundation model APIs at &lt;a href="https://menlovc.com/perspective/2025-the-state-of-generative-ai-in-the-enterprise/" rel="noopener noreferrer"&gt;12.5 billion dollars in 2025&lt;/a&gt;, roughly double the prior year, as workloads moved from experiments into production inference. When a line item doubles in a year, finance starts asking which team spent what, and "I am not sure, it is all one API key" is not an answer that survives a budget review. I work at a bank, and in a regulated shop that conversation arrives even faster, with the compliance team standing next to finance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an AI gateway, exactly?
&lt;/h2&gt;

&lt;p&gt;An AI gateway is a reverse proxy purpose-built for LLM traffic. It sits between your applications and the model providers they call, presents a single endpoint (usually shaped like the OpenAI API), and handles routing, fallbacks, token-based cost tracking, caching, guardrails, and logging on the way through. Your app talks to one address and stops caring which model is on the other side.&lt;/p&gt;

&lt;p&gt;Gartner frames it as an intermediary between applications and AI services that gives you a central point for the security, governance, and observability of AI workloads. That is the short version. The longer version is that the gateway becomes the one place where model access, cost, and policy are decided, instead of those decisions being scattered across every service that happens to call a model.&lt;/p&gt;

&lt;p&gt;Mechanically, a request flows like this. Your app sends an OpenAI-shaped completion request to the gateway. The gateway authenticates the caller against its own key registry, picks a provider based on your routing rules, injects the real provider credential (which your app never holds), forwards the request, then streams the response back while it counts tokens, applies content checks, and writes a log line. The application sees a normal API call. Everything interesting happens in the middle.&lt;/p&gt;

&lt;p&gt;Here is the part that makes the whole pattern click. You do not rewrite your code to adopt a gateway. You change the base URL.&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;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="c1"&gt;# Point the same OpenAI client at your gateway instead of the provider.
# Application code does not change. The gateway handles auth, routing,
# fallbacks, caching, and logging behind this single endpoint.
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-gateway.example.com/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GATEWAY_VIRTUAL_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# a scoped key, not your raw provider key
&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;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&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-sonnet-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# swap to gpt-4o or mistral-large, no other edits
&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize this contract clause.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;extra_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-gateway-team&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;payments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# cost attribution per team
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-gateway-fallback&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;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# backup model if the primary is down
&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="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&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;message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One virtual key, one base URL, and the model name becomes a value you can change without touching the rest of the request. That is the abstraction doing its job.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI gateway vs API gateway: what actually changes
&lt;/h2&gt;

&lt;p&gt;This is the question I get most, usually phrased as "we already run Kong, isn't an AI gateway just that with a new label." It is a fair question with a clear answer: the two share a shape and almost nothing else.&lt;/p&gt;

&lt;p&gt;Picture the difference physically. An API gateway treats the request body as a sealed envelope. It reads the address on the outside, routes by that, and never opens the letter. An AI gateway opens the envelope, reads the contents, counts the words, sometimes decides the answer is already cached and never sends it at all, and on the way back it can redact a line before handing the reply to you. One is a mail sorter. The other is a reader with opinions.&lt;/p&gt;

&lt;p&gt;That shift shows up across every dimension that matters in production.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;API Gateway&lt;/th&gt;
&lt;th&gt;AI Gateway&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit of metering&lt;/td&gt;
&lt;td&gt;Requests per minute&lt;/td&gt;
&lt;td&gt;Tokens per minute, plus spend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching&lt;/td&gt;
&lt;td&gt;Exact match by URL and headers&lt;/td&gt;
&lt;td&gt;Semantic, by meaning of the prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing&lt;/td&gt;
&lt;td&gt;Load balancing, path and header based&lt;/td&gt;
&lt;td&gt;Model aware: by cost, latency, capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response model&lt;/td&gt;
&lt;td&gt;Request and response, REST or GraphQL&lt;/td&gt;
&lt;td&gt;Streaming, token by token over SSE or WebSocket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure handling&lt;/td&gt;
&lt;td&gt;HTTP status codes, retry same target&lt;/td&gt;
&lt;td&gt;Fallback to a different model or provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security focus&lt;/td&gt;
&lt;td&gt;Who can call what&lt;/td&gt;
&lt;td&gt;PII redaction, prompt injection, content moderation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency budget&lt;/td&gt;
&lt;td&gt;Milliseconds&lt;/td&gt;
&lt;td&gt;Seconds, sometimes minutes for long generations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body&lt;/td&gt;
&lt;td&gt;Opaque, a black box&lt;/td&gt;
&lt;td&gt;Parsed, inspected, and rewritable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per call&lt;/td&gt;
&lt;td&gt;Roughly flat&lt;/td&gt;
&lt;td&gt;Highly variable, driven by token count&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The metering row is the one that bites first. A request count is the wrong ruler for LLM traffic. One short completion and one 4,000-token prompt with a 2,000-token answer both count as a single request, yet the second can cost roughly thirty times more, as the team at Fastio lays out for agent workloads. A request-per-minute limit will happily let a runaway agent spend your monthly budget before lunch, because every one of those expensive calls looks identical to a cheap one from where the API gateway is standing.&lt;/p&gt;

&lt;p&gt;This is also why bolting token logic onto a generic reverse proxy tends to end badly. You can write a Kong or NGINX plugin to count tokens, and it works right up until you hit streaming responses, where the tokens arrive in chunks and your counter has to track them mid-stream. At that point you have built a fragile version of a gateway instead of using one. In practice, teams running both put the API gateway at the perimeter as the front door for all services, and the AI gateway behind it, handling only the model traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core features worth paying for in an AI gateway
&lt;/h2&gt;

&lt;p&gt;Most gateways list thirty features. Six of them decide whether the thing survives contact with production. Here is what I check for, roughly in the order I care about it.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;unified, OpenAI-compatible API&lt;/strong&gt; is the floor, not a bonus. This is what lets you change one model name and move from Claude to GPT to Mistral with no other edit. One honest complaint here: "OpenAI-compatible" is a spectrum, not a guarantee. Providers differ on streaming details, tool-calling shapes, and error formats, and a gateway papers over more of that gap on some routes than others. Test the models you actually plan to use, not the ones in the marketing table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model-aware routing with real fallbacks&lt;/strong&gt; comes next. You want weighted routing (send 70 percent to one model, 30 percent to another), latency-based routing (whoever answers fastest), and health-aware failover that pulls a sick provider out automatically and tests for its recovery. Semantic routing, where the gateway reads the request and sends easy questions to a small cheap model and hard ones to a strong expensive model, is the most interesting version. It is also still an emerging capability in 2026 rather than a settled default, so treat vendor demos of it with a little skepticism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token-level cost tracking and budgets&lt;/strong&gt; is where the gateway pays for itself. The gateway should track consumption per key, per team, and per project, and enforce hard caps that reject or queue requests over the limit. This is the control that maps to how LLMs actually bill, and it is the one that keeps a misconfigured agent from becoming a finance incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic caching&lt;/strong&gt; is the quiet cost killer. Instead of matching on exact text, it compares the meaning of prompts and serves a stored answer for ones that are equivalent. A 2024 study on GPT Semantic Cache measured hit rates between 61 and 69 percent across common query categories, with cached answers returned in under 50 milliseconds against multi-second live calls. Teams commonly see 20 to 30 percent near-duplicate prompts across users, and caching those at the gateway cuts spend with zero application changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails at the trust boundary&lt;/strong&gt; matter more the more regulated you are. PII redaction strips sensitive data from a prompt before it ever reaches a provider. Prompt-injection detection blocks attempts to override your system instructions. Content moderation filters what comes back. The point is that this is the one place every request passes through, so it is the right place to enforce the policy once instead of in twelve services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability and governance&lt;/strong&gt; is the difference between a tool and infrastructure. Token-level logging, request tracing, virtual keys, role-based access control, and audit logs are what let you certify against SOC 2, GDPR, or the EU AI Act, whose high-risk obligations become enforceable from August 2026. If your gateway cannot tell you exactly what data left the building and which model received it, it is not ready for a regulated workload.&lt;/p&gt;

&lt;p&gt;One feature that does not show up on most checklists but should: &lt;strong&gt;low overhead at scale&lt;/strong&gt;. A gateway adds a hop, and that cost compounds. When an agent makes five sequential model calls and the gateway adds forty milliseconds each, that is two hundred milliseconds of pure proxy time in front of your user. For a single chat call it is invisible. For agent chains it is the kind of thing that shows up in your P99 and your conversion numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI gateway options exist in 2026?
&lt;/h2&gt;

&lt;p&gt;The field has matured fast, and it now splits cleanly into three camps. The honest summary: pick by deployment constraint and governance depth first, feature count second.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gateway&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Worth knowing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM&lt;/td&gt;
&lt;td&gt;Open-source, self-host&lt;/td&gt;
&lt;td&gt;A unified OpenAI-compatible API and full control&lt;/td&gt;
&lt;td&gt;Strong entry point, needs augmentation for regulated multi-team setups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bifrost (Maxim)&lt;/td&gt;
&lt;td&gt;Open-source, Go&lt;/td&gt;
&lt;td&gt;Low per-call overhead at high throughput&lt;/td&gt;
&lt;td&gt;Newer, performance-focused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare AI Gateway&lt;/td&gt;
&lt;td&gt;Managed, edge&lt;/td&gt;
&lt;td&gt;Edge caching with nothing to run&lt;/td&gt;
&lt;td&gt;Managed only, Cloudflare ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vercel AI Gateway&lt;/td&gt;
&lt;td&gt;Managed&lt;/td&gt;
&lt;td&gt;Teams already on Vercel and Next.js&lt;/td&gt;
&lt;td&gt;No token markup, limited routing and observability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;Hosted aggregator&lt;/td&gt;
&lt;td&gt;Breadth: many models, one key, one bill&lt;/td&gt;
&lt;td&gt;Light on governance, passes through provider pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portkey&lt;/td&gt;
&lt;td&gt;Managed plus self-host&lt;/td&gt;
&lt;td&gt;LLMOps: prompt management, guardrails, compliance&lt;/td&gt;
&lt;td&gt;Palo Alto Networks announced intent to acquire it in April 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TrueFoundry&lt;/td&gt;
&lt;td&gt;Control plane&lt;/td&gt;
&lt;td&gt;Enterprise governance in your own VPC or air-gapped&lt;/td&gt;
&lt;td&gt;Heavier to run, built for data residency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kong AI Gateway&lt;/td&gt;
&lt;td&gt;Plugin on Kong&lt;/td&gt;
&lt;td&gt;Teams standardized on Kong already&lt;/td&gt;
&lt;td&gt;Extends an existing API gateway with AI plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few signals tell you the market now treats this as core infrastructure rather than a convenience. A security vendor wiring a gateway into its platform is one bet on the layer's permanence. An aggregator raising serious money is another: OpenRouter raised a large round led by Alphabet's CapitalG in May 2026. Kong sizes the AI gateway market at 3.9 billion dollars in 2024, growing toward 9.8 billion by 2031. Money and acquisitions both point the same way, which is a reason to treat the choice as deliberate architecture, not a stopgap you bolt on once something breaks. If you want to read how one managed option handles the edge-caching angle, Cloudflare's &lt;a href="https://developers.cloudflare.com/ai-gateway/" rel="noopener noreferrer"&gt;AI Gateway docs&lt;/a&gt; are a clear primary source.&lt;/p&gt;

&lt;h2&gt;
  
  
  We already have an API gateway. Why add another hop?
&lt;/h2&gt;

&lt;p&gt;I will steelman the objection, because it is the right instinct. Every extra layer is latency, an operational burden, and one more thing that can fail in the middle of the night. A team that adds infrastructure it does not need is making a mistake, and a gateway in the hot path of an agent is a real cost, not a free abstraction.&lt;/p&gt;

&lt;p&gt;So here is the honest test. If you call exactly one provider, your token spend is modest, and you are still figuring out whether the feature even works, you probably do not need a gateway yet. Calling the provider SDK directly is simpler, and a gateway this early is the easiest abstraction to add too soon. I have watched teams reach for one before they had a second provider to route between, and all they bought was a hop.&lt;/p&gt;

&lt;p&gt;The objection stops holding the moment two things are true at once: you call more than one provider, and your spend is large enough that attribution, budgets, and caching stop being nice-to-haves. At that point the alternative to a gateway is not "no gateway." It is the same logic, smeared across every service, enforced inconsistently, and impossible to audit. The gateway is not extra work. It is the work, collected into one place where you can actually reason about it. And the API gateway you already run cannot do this job, because it was built to treat the request body as a black box, which is exactly the thing an AI gateway has to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for your stack
&lt;/h2&gt;

&lt;p&gt;Strip away the vendor noise and the decision comes down to a few real tradeoffs.&lt;/p&gt;

&lt;p&gt;The first is build versus buy. You can write a thin router yourself, and for a single team with one provider that might be the right call for a while. The cost of building shows up later, when you need semantic caching, audit-grade logging, and per-team budgets, and you find yourself maintaining a gateway as a side project instead of shipping product. Buy or adopt open-source once those concerns are real.&lt;/p&gt;

&lt;p&gt;The second is deployment, and this is where your industry decides for you. If you are in a regulated sector and customer data cannot leave your boundary, your shortlist is self-hosted (LiteLLM, Bifrost) or a control plane that runs in your VPC or air-gapped (TrueFoundry). If you live on Vercel, the Vercel gateway is the path of least resistance. If you want breadth fast with nothing to run, an aggregator like OpenRouter fills that role. If you have already standardized on Kong, its AI plugins extend what you have instead of adding a parallel system. Measuring spend cleanly per team is also the foundation for treating &lt;a href="https://dev.to[INTERNAL%20LINK]"&gt;token usage as a real engineering metric&lt;/a&gt;, which is hard to do when everything shares one key.&lt;/p&gt;

&lt;p&gt;The third is timing, and it is the one most teams get wrong in both directions. Too early and you carry overhead for an abstraction with nothing to abstract. Too late and you are retrofitting cost controls during the budget review that triggered the panic in the first place. The trigger to watch is your own: the second provider, or the spend threshold where finance starts asking questions. When you cross either line, that is the signal, not a calendar date. This decision sits right next to your &lt;a href="https://dev.to[INTERNAL%20LINK]"&gt;agentic CI and CD setup&lt;/a&gt;, since the same workflows that call models in production are usually the ones generating the spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions developers are actually asking about AI gateways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an AI gateway?
&lt;/h3&gt;

&lt;p&gt;An AI gateway is a reverse proxy that sits between your applications and one or more LLM providers, exposing a single, usually OpenAI-compatible endpoint. It centralizes routing, fallbacks, token-based cost tracking, semantic caching, guardrails, and observability so those concerns stay out of application code. Gartner describes it as a central control point for the security, governance, and observability of AI workloads. It is the equivalent of an API gateway, rebuilt for traffic that is metered in tokens and streamed one piece at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between an AI gateway and an API gateway?
&lt;/h3&gt;

&lt;p&gt;An API gateway routes and secures generic HTTP traffic, meters by request count, and caches by exact URL match. An AI gateway meters by tokens, caches by meaning through semantic caching, routes by model and provider, streams responses over Server-Sent Events, and adds AI-specific security like PII redaction and prompt-injection detection. A standard API gateway treats the request body as opaque, while an AI gateway reads and can rewrite it. Many teams run both: the API gateway at the perimeter, the AI gateway behind it for model traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need an AI gateway if I only use one LLM provider?
&lt;/h3&gt;

&lt;p&gt;Often not yet. With a single provider and modest token spend, calling the provider SDK directly is simpler, and adding a gateway too early introduces a hop and operational overhead you do not need. The case becomes strong once you call more than one provider, or your token bill grows large enough that cost attribution, budgets, and caching start to matter. Treat it as a deliberate architectural decision rather than a default.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does an AI gateway reduce LLM costs?
&lt;/h3&gt;

&lt;p&gt;It tracks token consumption per key, team, or project and enforces budgets, so a single misconfigured agent cannot quietly spend a month's allowance in an afternoon. Semantic caching returns stored answers for prompts that mean the same thing even when the wording differs, cutting both spend and latency. Model-aware routing can send simple requests to a smaller, cheaper model and reserve expensive models for hard ones. A 2024 GPT Semantic Cache study measured cache hit rates of 61 to 69 percent on common queries, with cached answers served in under 50 milliseconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is semantic caching?
&lt;/h3&gt;

&lt;p&gt;Semantic caching stores LLM responses and serves them for new prompts that are similar in meaning, not just identical in text. "Summarize this document" and "Give me a summary of this document" can hit the same cached answer, which exact-match caching would miss entirely. It works by comparing prompt embeddings rather than raw strings. For workloads with repeated query patterns it can remove a large share of live calls, since teams often see 20 to 30 percent near-duplicate prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the main AI gateway options in 2026?
&lt;/h3&gt;

&lt;p&gt;Open-source, self-hosted gateways like LiteLLM and Bifrost give you a unified API and full control of deployment. Managed services like Cloudflare AI Gateway, Vercel AI Gateway, and the OpenRouter aggregator give you breadth with nothing to run. LLMOps platforms like Portkey and enterprise control planes like TrueFoundry add governance, guardrails, and VPC or air-gapped deployment for regulated teams. Kong and Envoy extend existing API gateways with AI-specific plugins. The right pick depends on your deployment constraints, governance needs, and whether you want to own the layer or rent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layer that learned to think
&lt;/h2&gt;

&lt;p&gt;For most of computing history we worked hard to make the middle dumb. The best router was the one that moved bytes and got out of the way. The best gateway counted requests, checked a token, and made no judgments about what was passing through, because judgment was slow and the network was the bottleneck. We optimized the middle toward silence.&lt;/p&gt;

&lt;p&gt;LLMs inverted that. The expensive, slow, uncertain thing is no longer the network. It is the model. So the most consequential code in the stack is migrating to the one place that can see every request before it reaches that model and decide what it costs, whether it is safe, and whether it even needs to be asked. The gateway stopped being a pipe and became a place where decisions are made.&lt;/p&gt;

&lt;p&gt;That is a strange thing to say about a reverse proxy, and it is worth sitting with. Ten years from now, when someone asks where an organization's AI policy actually lives (not the document, the enforced reality of what data reaches which model under what budget), the answer will not be a meeting or a wiki. It will be a config in the gateway. The border we built to stay out of the way is becoming the part that thinks. Build it on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Menlo Ventures, 2025: The State of Generative AI in the Enterprise. &lt;a href="https://menlovc.com/perspective/2025-the-state-of-generative-ai-in-the-enterprise/" rel="noopener noreferrer"&gt;https://menlovc.com/perspective/2025-the-state-of-generative-ai-in-the-enterprise/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TrueFoundry, A Definitive Guide to AI Gateways in 2026 (cites the Gartner Market Guide for AI Gateways). &lt;a href="https://www.truefoundry.com/blog/a-definitive-guide-to-ai-gateways-in-2026-competitive-landscape-comparison" rel="noopener noreferrer"&gt;https://www.truefoundry.com/blog/a-definitive-guide-to-ai-gateways-in-2026-competitive-landscape-comparison&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kong, API Gateway vs AI Gateway. &lt;a href="https://konghq.com/blog/learning-center/api-gateway-vs--ai-gateway" rel="noopener noreferrer"&gt;https://konghq.com/blog/learning-center/api-gateway-vs--ai-gateway&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kong AI Gateway (product). &lt;a href="https://konghq.com/products/kong-ai-gateway" rel="noopener noreferrer"&gt;https://konghq.com/products/kong-ai-gateway&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Boomi, API Gateways vs AI Gateways. &lt;a href="https://boomi.com/blog/api-gateways-vs-ai-gateways/" rel="noopener noreferrer"&gt;https://boomi.com/blog/api-gateways-vs-ai-gateways/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API7.ai, What Is an AI Gateway? Architecture, Benefits and How It Works. &lt;a href="https://api7.ai/learning-center/api-gateway-guide/what-is-an-ai-gateway" rel="noopener noreferrer"&gt;https://api7.ai/learning-center/api-gateway-guide/what-is-an-ai-gateway&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MLflow, The Role of API Gateway AI Services in 2026. &lt;a href="https://mlflow.org/articles/the-role-of-api-gateway-ai-services-in-2026/" rel="noopener noreferrer"&gt;https://mlflow.org/articles/the-role-of-api-gateway-ai-services-in-2026/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Infrabase, AI Gateways Explained: When You Need One. &lt;a href="https://infrabase.ai/blog/ai-gateways-explained" rel="noopener noreferrer"&gt;https://infrabase.ai/blog/ai-gateways-explained&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fastio, 8 Best API Gateways for AI Agents in 2026 (semantic cache study figures). &lt;a href="https://fast.io/resources/best-api-gateways-ai-agents/" rel="noopener noreferrer"&gt;https://fast.io/resources/best-api-gateways-ai-agents/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloudflare, AI Gateway documentation. &lt;a href="https://developers.cloudflare.com/ai-gateway/" rel="noopener noreferrer"&gt;https://developers.cloudflare.com/ai-gateway/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LiteLLM documentation. &lt;a href="https://docs.litellm.ai/" rel="noopener noreferrer"&gt;https://docs.litellm.ai/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portkey. &lt;a href="https://portkey.ai/" rel="noopener noreferrer"&gt;https://portkey.ai/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Vercel AI Gateway documentation. &lt;a href="https://vercel.com/docs/ai-gateway" rel="noopener noreferrer"&gt;https://vercel.com/docs/ai-gateway&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenRouter. &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;https://openrouter.ai/&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Sayed Ali Alkamel is a Google Developer Expert in Dart and Flutter, co-founder of Flutter MENA, and Manager of Digital Application Platforms at Oman Housing Bank. He has spoken at tech events across 22+ countries and shipped apps with 2.5M+ downloads. He writes about Flutter, AI, and the developer experience at dev.to/sayed_ali_alkamel.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Flutter GenUI SDK: Build AI-Generated UIs from Scratch (Complete Beginner Tutorial 2026)</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Sat, 20 Jun 2026 09:42:14 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/flutter-genui-sdk-build-ai-generated-uis-from-scratch-complete-beginner-tutorial-2026-7ck</link>
      <guid>https://dev.to/sayed_ali_alkamel/flutter-genui-sdk-build-ai-generated-uis-from-scratch-complete-beginner-tutorial-2026-7ck</guid>
      <description>&lt;p&gt;Picture this. You open a language learning app. A Gemini model reads that you have been struggling with Arabic verb conjugation, and in under two seconds it assembles a bespoke lesson: a cultural scene-setter card, a triliteral root diagram showing every word in the family, a drag-and-drop vowel placement exercise, then a conjugation table. None of those screens were pre-designed. No developer wired them together for this session. The interface was composed at runtime, by the model itself, for you.&lt;/p&gt;

&lt;p&gt;That app is &lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;Kalaam · كلام&lt;/a&gt;, and the technology making it possible is the &lt;strong&gt;Flutter GenUI SDK&lt;/strong&gt;.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F59kkl3aqhv9exjqxcv9q.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F59kkl3aqhv9exjqxcv9q.png" alt="Kalaam home screen — type a learning goal or pick a real-world Arabic scenario" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kalaam's home screen: type any learning goal ("bargaining in a Cairo market"), or pick a pre-built real-world scenario. The rest — every widget, every step — is assembled live by Gemini.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This tutorial is your complete beginner's guide to building exactly this kind of app. By the end you will understand the five core concepts behind GenUI, you will have written your first custom widget the model can compose, and you will have a working open-source reference — Kalaam's full codebase — to study whenever you need to go deeper.&lt;/p&gt;

&lt;p&gt;No prior AI experience required. You need to know Flutter basics: widgets, &lt;code&gt;StatefulWidget&lt;/code&gt;, &lt;code&gt;pubspec.yaml&lt;/code&gt;. That is enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Flutter GenUI? The Real Explanation
&lt;/h2&gt;

&lt;p&gt;Most Flutter tutorials teach you to build a fixed UI. You define your widget tree, you compile it, and the app ships that exact structure to every user. The AI might power a search bar or a recommendation feed, but the interface itself is still pre-designed by a human developer.&lt;/p&gt;

&lt;p&gt;GenUI flips that model. Instead of the developer defining the UI, the developer defines a &lt;strong&gt;vocabulary of widgets&lt;/strong&gt;, and an AI model decides which widgets to assemble, in which order, with which data, based on what the user needs right now.&lt;/p&gt;

&lt;p&gt;The Flutter GenUI SDK (&lt;code&gt;genui&lt;/code&gt; on pub.dev, version 0.9.2, published by &lt;code&gt;labs.flutter.dev&lt;/code&gt;) is the official Flutter implementation of the A2UI v0.9 protocol. Announced at Google I/O 2026 alongside Flutter 3.44, it gives you the scaffolding to build apps where Gemini authors the UI at runtime.&lt;/p&gt;

&lt;p&gt;Here is the key insight that most explanations miss: &lt;strong&gt;the SDK does not call Gemini for you&lt;/strong&gt;. It does not know about Firebase, API keys, or networking. What it does is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take your catalog of widget definitions and embed their schemas into the Gemini system prompt, so the model knows what widgets it can create and what properties each one accepts.&lt;/li&gt;
&lt;li&gt;Parse the structured JSON Gemini streams back and turn it into live Flutter widgets, progressively, before the full response arrives.&lt;/li&gt;
&lt;li&gt;Keep a reactive data model that widgets can bind to, so the AI can push state updates without rebuilding the whole screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI writes the layout spec. The SDK renders it. You write the widgets.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4dtwus664k301zbko3hs.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4dtwus664k301zbko3hs.png" alt="Live GenUI Inspector — watch Gemini compose the Flutter widget tree token by token" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tap the &lt;code&gt;{}&lt;/code&gt; button and watch Gemini write your UI in real time. Every field in that JSON panel became a widget on screen above it.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Alpha warning:&lt;/strong&gt; GenUI is marked "highly experimental" by the Flutter team. Pin to &lt;code&gt;^0.9.2&lt;/code&gt; (not &lt;code&gt;any&lt;/code&gt;) in your &lt;code&gt;pubspec.yaml&lt;/code&gt; and subscribe to the &lt;a href="https://github.com/flutter/genui/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG on GitHub&lt;/a&gt;. APIs will change between minor versions. Build something real with it today, but expect to update call sites when new versions land.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The A2UI Protocol: What Is Actually Happening Under the Hood
&lt;/h2&gt;

&lt;p&gt;Every existing GenUI tutorial treats the JSON protocol as a black box. Open it once and the entire SDK becomes obvious.&lt;/p&gt;

&lt;p&gt;When your app sends a message to Gemini, the model does not respond with plain text. Because of the system prompt GenUI generates, it responds with structured JSON messages following the A2UI v0.9 wire format. These messages tell the SDK what to render. There are four types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;createSurface&lt;/code&gt;&lt;/strong&gt; — Gemini is creating a new UI area (think: a new step in your lesson).&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"createSurface"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surfaceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turn_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;"components"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"root_explorer_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;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RootExplorer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rootWord"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"rootMeaning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"to write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"family"&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;"word"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"transliteration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kataba"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"meaning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"he wrote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;surfaceUpdate&lt;/code&gt;&lt;/strong&gt; — Gemini is modifying an existing surface in place. No screen rebuild, no transition.&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"surfaceUpdate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surfaceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turn_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;"components"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"feedback_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Correct! كَتَبَ follows the فَعَلَ pattern."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;dataModelUpdate&lt;/code&gt;&lt;/strong&gt; — Gemini is pushing new state to the reactive data store. Any widget bound to that path rebuilds automatically.&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dataModelUpdate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updates"&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;"learner/accuracy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"session/wordsCorrect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;deleteSurface&lt;/code&gt;&lt;/strong&gt; — Remove a surface when a lesson step is done.&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deleteSurface"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surfaceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"turn_1"&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;Those four messages are everything GenUI understands. The SDK's job is to listen to Gemini's stream, parse these messages as tokens arrive (before the full response is complete), and apply them to the widget tree. Now that you know what is happening at the wire level, every class in the SDK makes immediate sense.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fve2a3ubnss795jyrilti.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fve2a3ubnss795jyrilti.png" alt="Kalaam's Live GenUI Inspector showing a CREATE turn_2 message with full A2UI JSON" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kalaam's Live Inspector mid-lesson. The &lt;code&gt;CREATE turn_2&lt;/code&gt; badge on the left is a &lt;code&gt;createSurface&lt;/code&gt; message. The fields below it — &lt;code&gt;"version": "v0.9"&lt;/code&gt;, &lt;code&gt;"surfaceId": "turn_2"&lt;/code&gt; — are the start of the Root Explorer component JSON. The Root Explorer you see rendered above the panel is what those fields became.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 8-Step Interaction Cycle
&lt;/h2&gt;

&lt;p&gt;Every interaction in a GenUI app follows the same loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User types or taps something&lt;/li&gt;
&lt;li&gt;Your app calls &lt;code&gt;conversation.sendRequest(content)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Conversation&lt;/code&gt; triggers &lt;code&gt;A2uiTransportAdapter&lt;/code&gt;'s &lt;code&gt;onSend&lt;/code&gt; callback&lt;/li&gt;
&lt;li&gt;Your callback calls Gemini and pipes each streaming token to &lt;code&gt;_transport.addChunk(chunk)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;A2uiParserTransformer&lt;/code&gt; parses the streaming JSON in real time&lt;/li&gt;
&lt;li&gt;Parsed &lt;code&gt;A2uiMessage&lt;/code&gt; objects feed into &lt;code&gt;SurfaceController.handleMessage()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SurfaceController&lt;/code&gt; updates the &lt;code&gt;DataModel&lt;/code&gt; and its &lt;code&gt;Surface&lt;/code&gt; widgets rebuild&lt;/li&gt;
&lt;li&gt;User taps a generated widget, &lt;code&gt;UserActionEvent&lt;/code&gt; is dispatched, &lt;code&gt;SurfaceController.onSubmit&lt;/code&gt; emits, &lt;code&gt;Conversation&lt;/code&gt; wraps it as a new user turn, and the cycle restarts from step 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every API call you will make maps to one step in this loop. Keep the cycle in mind as you read the sections below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User input
    │
    ▼
conversation.sendRequest()
    │
    ▼
A2uiTransportAdapter.onSend  ──────►  Gemini API (your code)
                                              │ streams chunks
    ◄─────────────────────────────────────────┘
    │
    ▼
A2uiTransportAdapter.addChunk()
    │  (parses streaming JSON)
    ▼
SurfaceController.handleMessage()
    │
    ├─► createSurface   ──► Surface widget added to screen
    ├─► surfaceUpdate   ──► Surface widget updated in place
    ├─► dataModelUpdate ──► Bound widgets rebuild automatically
    └─► deleteSurface   ──► Surface widget removed
    │
    ▼ (user taps generated widget)
UserActionEvent ──► conversation.sendRequest() ──► loop restarts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Prerequisites and Project Setup
&lt;/h2&gt;

&lt;p&gt;Before writing any GenUI code, confirm you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter 3.44+ (run &lt;code&gt;flutter --version&lt;/code&gt; to check)&lt;/li&gt;
&lt;li&gt;Dart 3.9+&lt;/li&gt;
&lt;li&gt;A Firebase project with &lt;a href="https://firebase.google.com/docs/ai-logic/get-started" rel="noopener noreferrer"&gt;AI Logic enabled&lt;/a&gt; — Gemini Developer API is on the free tier&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;flutterfire_cli&lt;/code&gt; tool: &lt;code&gt;dart pub global activate flutterfire_cli&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install the dependencies
&lt;/h3&gt;

&lt;p&gt;Add to &lt;code&gt;pubspec.yaml&lt;/code&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="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;
  &lt;span class="na"&gt;genui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.9.2&lt;/span&gt;
  &lt;span class="na"&gt;firebase_core&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.10.0&lt;/span&gt;
  &lt;span class="na"&gt;firebase_ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.5.0&lt;/span&gt;
  &lt;span class="na"&gt;json_schema_builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;flutter pub get&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Firebase
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;flutterfire configure&lt;/code&gt; and follow the prompts. It generates &lt;code&gt;lib/firebase_options.dart&lt;/code&gt;. This file contains your app's credentials and must never be committed. Add these lines to &lt;code&gt;.gitignore&lt;/code&gt; right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/firebase_options.dart
android/app/google-services.json
ios/Runner/GoogleService-Info.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  iOS and macOS network entitlement (a step almost every tutorial skips)
&lt;/h3&gt;

&lt;p&gt;If you target iOS or macOS, your app will silently fail to reach Gemini without this entitlement. Add it once and forget about it.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;ios/Runner/Runner.entitlements&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.security.network.client&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;macos/Runner/DebugProfile.entitlements&lt;/code&gt; and &lt;code&gt;Release.entitlements&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;com.apple.security.network.client&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;true/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Initialize Firebase
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:firebase_core/firebase_core.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'firebase_options.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;WidgetsFlutterBinding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ensureInitialized&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Firebase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;options:&lt;/span&gt; &lt;span class="n"&gt;DefaultFirebaseOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentPlatform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&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;h2&gt;
  
  
  The Five Core Concepts You Must Understand
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. CatalogItem — Defining What the AI Can Build
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;CatalogItem&lt;/code&gt; is the fundamental unit of GenUI. It tells the SDK (and therefore Gemini) that a widget named &lt;code&gt;X&lt;/code&gt; exists, what JSON properties it accepts, and how to render it as a Flutter widget.&lt;/p&gt;

&lt;p&gt;Here is a minimal but complete example — a simple Arabic flashcard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:genui/genui.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:json_schema_builder/json_schema_builder.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;flashcardItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CatalogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'ArabicFlashcard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;dataSchema:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'arabic'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Arabic word with full diacritics'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'transliteration'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Romanised pronunciation'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'English translation'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'arabic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'transliteration'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;exampleData:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;'''
[
  {
    "id": "card_1",
    "component": "ArabicFlashcard",
    "arabic": "كَتَبَ",
    "transliteration": "kataba",
    "meaning": "he wrote"
  }
]
'''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;widgetBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;mainAxisSize:&lt;/span&gt; &lt;span class="n"&gt;MainAxisSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;children:&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;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'arabic'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&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;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'transliteration'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&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;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;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;Three things are happening here, and all three matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;dataSchema&lt;/code&gt;&lt;/strong&gt; defines the JSON the model must provide when it wants to create this widget. The &lt;code&gt;json_schema_builder&lt;/code&gt; package gives you a type-safe DSL (&lt;code&gt;S.object&lt;/code&gt;, &lt;code&gt;S.string&lt;/code&gt;, &lt;code&gt;S.list&lt;/code&gt;, &lt;code&gt;S.boolean&lt;/code&gt;) for writing JSON Schema. GenUI embeds this schema into the system prompt so Gemini knows which fields are valid and required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;exampleData&lt;/code&gt;&lt;/strong&gt; contains one or more example JSON snippets. GenUI uses these as few-shot examples in the system prompt, teaching the model the correct format by demonstration rather than by description alone. This is the single biggest lever for getting consistent, valid output from the model. A good example is worth more than a detailed description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;widgetBuilder&lt;/code&gt;&lt;/strong&gt; is the Flutter side. It receives a &lt;code&gt;CatalogItemContext&lt;/code&gt; containing &lt;code&gt;ctx.data&lt;/code&gt; (the parsed JSON), &lt;code&gt;ctx.dataContext&lt;/code&gt; (access to the reactive data model), and &lt;code&gt;ctx.dispatchEvent(...)&lt;/code&gt; (to send user interactions back to Gemini). Return any Flutter widget. Keep &lt;code&gt;mainAxisSize: MainAxisSize.min&lt;/code&gt; on columns and rows — GenUI embeds your widget inside a dynamically-sized surface, and unbounded height constraints will crash it.&lt;/p&gt;

&lt;p&gt;Here is what a real &lt;code&gt;CatalogItem&lt;/code&gt; looks like rendered — Kalaam's Root System Explorer, generated from a single &lt;code&gt;RootExplorer&lt;/code&gt; component in a &lt;code&gt;createSurface&lt;/code&gt; message:&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff1p3azr9b8b3oh539l2f.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff1p3azr9b8b3oh539l2f.png" alt="Root System Explorer — all nodes collapsed, ش-ر-ب root with five derived words radiating outward" width="390" height="848"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every node, connector line, and label comes from the JSON Gemini provided in the &lt;code&gt;createSurface&lt;/code&gt; message. The &lt;code&gt;widgetBuilder&lt;/code&gt; turns that JSON into this radial diagram.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Catalog — Grouping Your CatalogItems
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;Catalog&lt;/code&gt; is a named collection of &lt;code&gt;CatalogItem&lt;/code&gt;s. You combine your custom items with the built-in primitives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;appCatalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Catalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;items:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;flashcardItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BasicCatalogItems&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&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;&lt;code&gt;BasicCatalogItems.all()&lt;/code&gt; gives you 17 built-in widgets for free: &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;Column&lt;/code&gt;, &lt;code&gt;Row&lt;/code&gt;, &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;TextField&lt;/code&gt;, &lt;code&gt;AudioPlayer&lt;/code&gt;, &lt;code&gt;Tabs&lt;/code&gt;, &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;Image&lt;/code&gt;, &lt;code&gt;Icon&lt;/code&gt;, &lt;code&gt;Divider&lt;/code&gt;, &lt;code&gt;Slider&lt;/code&gt;, &lt;code&gt;ChoicePicker&lt;/code&gt;, &lt;code&gt;CheckBox&lt;/code&gt;, &lt;code&gt;DateTimeInput&lt;/code&gt;, and &lt;code&gt;Modal&lt;/code&gt;. Gemini can use both your custom widgets and the primitives in the same layout. A single &lt;code&gt;createSurface&lt;/code&gt; message might compose a &lt;code&gt;Column&lt;/code&gt; containing an &lt;code&gt;ArabicFlashcard&lt;/code&gt; followed by a &lt;code&gt;Button&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Kalaam exposes a combined catalog of all 13 custom Arabic teaching widgets plus all built-in primitives, defined in &lt;a href="https://github.com/sayed3li97/kalaam/blob/main/lib/features/session/catalog/catalog.dart" rel="noopener noreferrer"&gt;&lt;code&gt;lib/features/session/catalog/catalog.dart&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SurfaceController — The Runtime Engine
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SurfaceController&lt;/code&gt; is the brain of a GenUI session. It processes incoming &lt;code&gt;A2uiMessage&lt;/code&gt; objects, manages the reactive &lt;code&gt;DataModel&lt;/code&gt;, and broadcasts events when the user interacts with generated widgets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SurfaceController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;catalogs:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;appCatalog&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Surface&lt;/code&gt; widget renders whatever the model has built for a given surface ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Surface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;host:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;surfaceId:&lt;/span&gt; &lt;span class="s"&gt;'turn_1'&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;code&gt;Surface&lt;/code&gt; listens to &lt;code&gt;SurfaceController&lt;/code&gt; and rebuilds whenever Gemini sends a &lt;code&gt;surfaceUpdate&lt;/code&gt; or &lt;code&gt;dataModelUpdate&lt;/code&gt; for that surface ID. You do not manage this rebuild yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. A2uiTransportAdapter — The Streaming Bridge
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;A2uiTransportAdapter&lt;/code&gt; bridges the gap between raw LLM token chunks and parsed &lt;code&gt;A2uiMessage&lt;/code&gt; objects. You create it with an &lt;code&gt;onSend&lt;/code&gt; callback that fires whenever &lt;code&gt;Conversation&lt;/code&gt; wants to send a message to Gemini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A2uiTransportAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;onSend:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateContentStream&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="k"&gt;await&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;final&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;finishSending&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 token from Gemini passes through &lt;code&gt;transport.addChunk()&lt;/code&gt;. The adapter parses the streaming JSON incrementally and emits complete &lt;code&gt;A2uiMessage&lt;/code&gt; objects as soon as they are parseable. This is why surfaces appear progressively as Gemini generates them, not all at once when the full response arrives.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;transport.incomingMessages&lt;/code&gt; is a stream of &lt;code&gt;A2uiMessage&lt;/code&gt; objects you can tap independently for logging or inspection. Kalaam uses this to power its Live GenUI Inspector.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Conversation — The Top-Level Facade
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Conversation&lt;/code&gt; wires &lt;code&gt;SurfaceController&lt;/code&gt; and &lt;code&gt;A2uiTransportAdapter&lt;/code&gt; together and manages conversation history. It is the only object your UI layer needs to hold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;transport:&lt;/span&gt; &lt;span class="n"&gt;transport&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;Send a user message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'teach me the root ك-ت-ب'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Conversation&lt;/code&gt; builds the full &lt;code&gt;List&amp;lt;Content&amp;gt;&lt;/code&gt; history (all prior turns) and passes it to your &lt;code&gt;onSend&lt;/code&gt; callback, so Gemini always has the full context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building Your First GenUI App Step by Step
&lt;/h2&gt;

&lt;p&gt;Here is what the finished app looks like: a Gemini-composed lesson flowing from a vocab carousel through the Root System Explorer, with the QuickChoice quiz at the end.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwc3kv5bmlq16zn6wrk6l.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwc3kv5bmlq16zn6wrk6l.png" alt="Full Kalaam session flow — home screen to vocab carousel to Root Explorer to quiz, all UI composed by Gemini at runtime" width="400" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Home screen → Begin lesson → VocabCarousel → Root System Explorer → QuickChoice quiz. None of these transitions were pre-wired — Gemini assembled them from the catalog.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's put all five concepts together. This is a minimal, complete, runnable example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the catalog
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/catalog.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:genui/genui.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:json_schema_builder/json_schema_builder.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;infoCardItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CatalogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'InfoCard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;dataSchema:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'title'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Card heading'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'body'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Card body text'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;exampleData:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;'''
[{"id":"c1","component":"InfoCard","title":"Hello","body":"GenUI is working."}]
'''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nl"&gt;widgetBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;mainAxisSize:&lt;/span&gt; &lt;span class="n"&gt;MainAxisSize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;crossAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;CrossAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;children:&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;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TextStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;fontSize:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nl"&gt;fontWeight:&lt;/span&gt; &lt;span class="n"&gt;FontWeight&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;w600&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="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;8&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;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'body'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;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="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;appCatalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Catalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;items:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;infoCardItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BasicCatalogItems&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&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 2: Wire the session screen
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/session_screen.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:firebase_ai/firebase_ai.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:genui/genui.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'catalog.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SessionScreen&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SessionScreen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SessionScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_SessionScreenState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_SessionScreenState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SessionScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;SurfaceController&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;A2uiTransportAdapter&lt;/span&gt; &lt;span class="n"&gt;_transport&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Conversation&lt;/span&gt; &lt;span class="n"&gt;_conversation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt; &lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;_textController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_surfaceIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;_controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SurfaceController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;catalogs:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;appCatalog&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Track new surfaces so we can render them&lt;/span&gt;
    &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;surfaces&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;surfaces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_surfaceIds&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;surfaces&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;keys&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="c1"&gt;// Build system prompt from catalog&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;PromptBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;catalog:&lt;/span&gt; &lt;span class="n"&gt;appCatalog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemPromptJoined&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="n"&gt;FirebaseAI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;googleAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;model:&lt;/span&gt; &lt;span class="s"&gt;'gemini-2.5-flash'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;systemInstruction:&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;_transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A2uiTransportAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;onSend:&lt;/span&gt; &lt;span class="n"&gt;_sendToGemini&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;_conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;transport:&lt;/span&gt; &lt;span class="n"&gt;_transport&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="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sendToGemini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateContentStream&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="k"&gt;await&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;final&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Show an error banner in your UI here&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;finishSending&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="n"&gt;mounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_waiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;_send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_textController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&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="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&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="n"&gt;_textController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_conversation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&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;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_conversation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;_textController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&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="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'GenUI Demo'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;_surfaceIds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;bottom:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Surface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;host:&lt;/span&gt; &lt;span class="n"&gt;_controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;surfaceId:&lt;/span&gt; &lt;span class="n"&gt;_surfaceIds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;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="n"&gt;_waiting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;LinearProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;Expanded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;_textController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nl"&gt;decoration:&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;hintText:&lt;/span&gt; &lt;span class="s"&gt;'Ask Gemini...'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nl"&gt;onSubmitted:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_send&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="n"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;_send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&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;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;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;Type "show me an info card about Flutter" and Gemini creates an &lt;code&gt;InfoCard&lt;/code&gt; surface. Type "update it to be about Dart" and Gemini sends a &lt;code&gt;surfaceUpdate&lt;/code&gt; modifying the same surface in place. The AI is composing and mutating your widget tree.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PromptBuilder token count:&lt;/strong&gt; the system prompt GenUI generates from your catalog is large — typically 3,000 to 5,000 tokens, depending on how many catalog items and how detailed your schemas are. Every message to Gemini includes this full prompt. Plan for it in your cost estimates. Very small or local models will not have sufficient context window or instruction-following capability to handle it. To reduce token count, use &lt;code&gt;PromptBuilder.custom()&lt;/code&gt; with a hand-written prompt, or pass only essential extra instructions via &lt;code&gt;systemPromptFragments&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Custom CatalogItem Deep Dive: The Root System Explorer
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;InfoCard&lt;/code&gt; above is easy to follow. Kalaam's &lt;code&gt;RootExplorer&lt;/code&gt; — a radial diagram of Arabic root words — is a production example showing how far you can push a single &lt;code&gt;CatalogItem&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Look at the schema alone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;rootExplorerItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CatalogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'RootExplorer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;dataSchema:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'rootWord'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'The triliteral root, letters joined by dashes, e.g. ك-ت-ب'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'rootMeaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Core meaning of the root in English'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'family'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Derived words that share this root'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;minItems:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;maxItems:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;items:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;'word'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Derived word with full harakat'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;'transliteration'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Romanised pronunciation'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'English meaning'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;'pattern'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Morphological pattern (wazn), e.g. فَعَلَ, مَفْعَل, فَاعِل'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;'isExpanded'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'DataModel-bound, false initially'&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="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'word'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'transliteration'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'meaning'&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;span class="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'rootWord'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'rootMeaning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'family'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// ... widgetBuilder renders the radial diagram&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three patterns here that apply to any production &lt;code&gt;CatalogItem&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nested schemas for arrays.&lt;/strong&gt; &lt;code&gt;S.list(items: S.object(...))&lt;/code&gt; lets you define complex nested structures. Gemini learns to produce the entire &lt;code&gt;family&lt;/code&gt; array with all sub-fields from a single example, without any additional instruction. The schema and the example together are the full specification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DataModel binding hints in descriptions.&lt;/strong&gt; The &lt;code&gt;isExpanded&lt;/code&gt; field description says "DataModel-bound, false initially." This is a hint to Gemini that it can send a &lt;code&gt;dataModelUpdate&lt;/code&gt; targeting this field's path to expand or collapse specific nodes without rebuilding the surface. The description field in your schema is part of the model's instruction set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passing &lt;code&gt;ctx&lt;/code&gt; down the widget tree.&lt;/strong&gt; The &lt;code&gt;widgetBuilder&lt;/code&gt; passes &lt;code&gt;CatalogItemContext&lt;/code&gt; to child widgets. The individual radial nodes use &lt;code&gt;ctx.dataContext&lt;/code&gt; for binding and &lt;code&gt;ctx.dispatchEvent(...)&lt;/code&gt; for the Explore button. Keep &lt;code&gt;ctx&lt;/code&gt; accessible throughout every level of your widget tree that needs to interact with GenUI.&lt;/p&gt;

&lt;p&gt;The full 498-line implementation lives at &lt;a href="https://github.com/sayed3li97/kalaam/blob/main/lib/features/session/catalog/items/root_explorer_item.dart" rel="noopener noreferrer"&gt;&lt;code&gt;lib/features/session/catalog/items/root_explorer_item.dart&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is that same widget with a node tapped — the &lt;code&gt;isExpanded&lt;/code&gt; DataModel path flipped to &lt;code&gt;true&lt;/code&gt;, revealing the وزن pattern badge and Explore button without any surface rebuild:&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fak9tg8f230fj2725oytr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fak9tg8f230fj2725oytr.png" alt="Root System Explorer node expanded — showing وزن مَفْعُول badge and Explore arrow button" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tapping مَشْرُوب expands it to reveal its morphological pattern (وزن مَفْعُول) and an Explore→ button that asks Gemini to branch deeper into that word. The animation is an &lt;code&gt;AnimatedContainer&lt;/code&gt; reacting to local state — no GenUI rebuild needed.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  DataModel Binding with &lt;code&gt;A2uiSchemas.stringReference&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The DataModel is where GenUI starts to feel like magic. Gemini writes to it with &lt;code&gt;dataModelUpdate&lt;/code&gt; messages and your widgets react automatically — no &lt;code&gt;setState&lt;/code&gt;, no &lt;code&gt;StreamBuilder&lt;/code&gt; wiring on your part.&lt;/p&gt;

&lt;p&gt;Kalaam's &lt;code&gt;QuickChoice&lt;/code&gt; widget (multiple-choice quiz) uses a more advanced binding pattern than &lt;code&gt;S.boolean&lt;/code&gt;: &lt;code&gt;A2uiSchemas.stringReference&lt;/code&gt;. This tells Gemini that a field is not a static value but a live DataModel path it can write to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;quickChoiceItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CatalogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'QuickChoice'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;dataSchema:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'question'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;A2uiSchemas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stringReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'The multiple choice question'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'options'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Array of 4 options'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;minItems:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;maxItems:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;items:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Option identifier (A, B, C, or D)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s"&gt;'text'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Option content text'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'text'&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="s"&gt;'correctId'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'The correct option ID'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'selectedId'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;A2uiSchemas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stringReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'DataModel-bound chosen option ID'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'explanationOnWrong'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;A2uiSchemas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stringReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Explanation shown if user chooses wrong answer'&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="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'question'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'options'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'correctId'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'explanationOnWrong'&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;The &lt;code&gt;selectedId&lt;/code&gt; field is declared as a &lt;code&gt;stringReference&lt;/code&gt;. When Gemini creates the surface it provides a DataModel path like &lt;code&gt;session/quiz_1/selected&lt;/code&gt;. When the user taps an option, the widget writes the option ID to that path. When Gemini wants to reveal the correct/incorrect state, it sends a &lt;code&gt;dataModelUpdate&lt;/code&gt; with &lt;code&gt;session/quiz_1/selected = "B"&lt;/code&gt; and the widget rebuilds automatically to show the result.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;widgetBuilder&lt;/code&gt;, you consume a &lt;code&gt;stringReference&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;selectedIdRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'selectedId'&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="n"&gt;selectedIdRef&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;selectedIdRef&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;containsKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'path'&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="n"&gt;BoundString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;dataContext:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dataContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;value:&lt;/span&gt; &lt;span class="n"&gt;selectedIdRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selectedId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Rebuild whenever Gemini sends dataModelUpdate for this path&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_QuizWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;selectedId:&lt;/span&gt; &lt;span class="n"&gt;selectedId&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BoundString&lt;/code&gt;, &lt;code&gt;BoundBool&lt;/code&gt;, &lt;code&gt;BoundNumber&lt;/code&gt;, and &lt;code&gt;BoundList&lt;/code&gt; are all built into GenUI. They listen to the DataModel path and call their &lt;code&gt;builder&lt;/code&gt; with the updated value whenever Gemini (or your own widget code) writes to that path.&lt;/p&gt;

&lt;p&gt;This pattern — Gemini setting a path, a widget binding to it, and the widget reacting without any app-level &lt;code&gt;setState&lt;/code&gt; — is the cleanest way to handle interactive state in GenUI apps. Kalaam uses it for quiz selection, mastery ring progress, and the expanded/collapsed state of each node in the Root Explorer.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6k06eeh9fwz9y9hwrltj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6k06eeh9fwz9y9hwrltj.png" alt="Root Explorer expanded node — isExpanded DataModel path flipped to true, revealing وزن badge" width="390" height="848"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The expanded state is a DataModel-bound &lt;code&gt;BoundBool&lt;/code&gt;. Gemini can expand or collapse any node mid-lesson by sending a &lt;code&gt;dataModelUpdate&lt;/code&gt; — without touching the surface or rebuilding any other widget.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Handling User Interactions with UserActionEvent
&lt;/h2&gt;

&lt;p&gt;When a user taps a generated widget, you need to send that event back to Gemini so it can decide what to do next. &lt;code&gt;UserActionEvent&lt;/code&gt; is the mechanism.&lt;/p&gt;

&lt;p&gt;Kalaam centralises this in a small helper that every catalog item uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/features/session/catalog/kalaam_actions.dart&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:genui/genui.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sendKalaamAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;CatalogItemContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;payload&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="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;UserActionEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;sourceComponentId:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;componentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;context:&lt;/span&gt; &lt;span class="n"&gt;payload&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;The Root Explorer's Explore button uses it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;onTap:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sendKalaamAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;'explore_word'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'word'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'root'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rootWord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meaning&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Explore →'&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;When the user taps, &lt;code&gt;SurfaceController.onSubmit&lt;/code&gt; emits a &lt;code&gt;UserActionEvent&lt;/code&gt;. &lt;code&gt;Conversation&lt;/code&gt; wraps it as a &lt;code&gt;UiInteractionMessage&lt;/code&gt; and sends it back to Gemini as the next user turn. Gemini receives the action name and payload as context and might respond by creating a new &lt;code&gt;VocabCard&lt;/code&gt; surface for that specific word, or a &lt;code&gt;ConjugationTable&lt;/code&gt; for its verb forms.&lt;/p&gt;

&lt;p&gt;The event name (&lt;code&gt;'explore_word'&lt;/code&gt;) is a contract. You define it in your system prompt fragments (to teach the model what actions exist), and you dispatch it from your widget. Keep names specific and documented.&lt;/p&gt;

&lt;p&gt;Here is &lt;code&gt;UserActionEvent&lt;/code&gt; in practice — the &lt;code&gt;QuickChoice&lt;/code&gt; quiz widget dispatching a correct or incorrect answer, which causes Gemini to generate the next lesson surface:&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp4pf6vi4owpufzsrg0z.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpp4pf6vi4owpufzsrg0z.png" alt="QuickChoice quiz interaction — incorrect answer highlights red, correct answer highlights green, explanation appears below" width="400" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tap "Tea" → red highlight (wrong). Correct answer "Coffee" turns green immediately. Gemini receives &lt;code&gt;{isCorrect: false}&lt;/code&gt; and tailors the next turn.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Live GenUI Inspector: Watching Gemini Build the UI
&lt;/h2&gt;

&lt;p&gt;One of the most instructive patterns in Kalaam is the Live GenUI Inspector — a slide-up panel that streams the raw A2UI JSON messages Gemini emits, displayed as pretty-printed text. It is not part of the SDK. It is ten lines of code using &lt;code&gt;_transport.incomingMessages&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your service / state holder:&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;a2uiLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ValueNotifier&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;A2uiLogEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;([]);&lt;/span&gt;

&lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;StreamSubscription&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;A2uiMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logSub&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After creating _transport:&lt;/span&gt;
&lt;span class="n"&gt;_logSub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incomingMessages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A2uiLogEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;kind:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// createSurface | surfaceUpdate | etc.&lt;/span&gt;
    &lt;span class="nl"&gt;surfaceId:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;surfaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;json:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;JsonEncoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withIndent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'  '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;a2uiLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[..&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;a2uiLog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&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;The inspector panel is a &lt;code&gt;ValueListenableBuilder&lt;/code&gt; over this notifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;ValueListenableBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;A2uiLogEntry&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;valueListenable:&lt;/span&gt; &lt;span class="n"&gt;a2uiLog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;builder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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="n"&gt;ListView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;reverse:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;itemCount:&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;itemBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_LogTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;entry:&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern has a purpose beyond debugging: it makes the GenUI data flow visible to other developers. The moment someone watches a &lt;code&gt;CREATE turn_2&lt;/code&gt; badge appear and immediately sees the corresponding Root Explorer render on screen, the whole concept of AI-generated UI clicks. Kalaam ships this in production precisely for that reason.&lt;/p&gt;

&lt;p&gt;Full implementation at &lt;a href="https://github.com/sayed3li97/kalaam/blob/main/lib/features/session/view/widgets/genui_inspector.dart" rel="noopener noreferrer"&gt;&lt;code&gt;lib/features/session/view/widgets/genui_inspector.dart&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7jqgvdjy7nxajshmxhwx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7jqgvdjy7nxajshmxhwx.png" alt="Live GenUI Inspector streaming — CREATE turn_2 badge appearing as Gemini assembles the Root Explorer widget" width="400" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Inspector streaming A2UI JSON alongside the live lesson. The &lt;code&gt;CREATE turn_2&lt;/code&gt; badge marks the Root Explorer surface. Every field you see was streamed token by token from Gemini.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging GenUI Apps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Enable SDK logging
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;configureLogging()&lt;/code&gt; function is your most powerful debugging tool. Add it before &lt;code&gt;runApp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:genui/genui.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:logging/logging.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configureLogging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;level:&lt;/span&gt; &lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onRecord&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'[genui] &lt;/span&gt;&lt;span class="si"&gt;${record.level.name}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${record.message}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Level.ALL&lt;/code&gt; you see each raw JSON chunk as it arrives, when the parser detects a new message type, when &lt;code&gt;SurfaceController&lt;/code&gt; creates or updates a surface, DataModel path updates, and any parse errors with the offending fragment. Drop to &lt;code&gt;Level.INFO&lt;/code&gt; in release builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inspect the generated system prompt
&lt;/h3&gt;

&lt;p&gt;Run this once to understand what the model sees and why prompt token count matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PromptBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;catalog:&lt;/span&gt; &lt;span class="n"&gt;appCatalog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;systemPromptJoined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Prompt length: &lt;/span&gt;&lt;span class="si"&gt;${prompt.length}&lt;/span&gt;&lt;span class="s"&gt; chars'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Token estimate: &lt;/span&gt;&lt;span class="si"&gt;${prompt.length ~/ 4}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common errors and fixes
&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;Most likely 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;Surfaces never appear&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;onSend&lt;/code&gt; not calling &lt;code&gt;_transport.addChunk()&lt;/code&gt; on every chunk&lt;/td&gt;
&lt;td&gt;Verify your &lt;code&gt;await for&lt;/code&gt; loop reaches every streamed token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS/macOS network fails silently&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;com.apple.security.network.client&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Add the entitlement to both Debug and Release entitlement files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini returns plain text&lt;/td&gt;
&lt;td&gt;System prompt not passed to the model&lt;/td&gt;
&lt;td&gt;Confirm &lt;code&gt;systemInstruction: Content.system(systemPrompt)&lt;/code&gt; is in &lt;code&gt;generativeModel(...)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;setState called after dispose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Listening to &lt;code&gt;_controller.surfaces&lt;/code&gt; without canceling&lt;/td&gt;
&lt;td&gt;Store the &lt;code&gt;StreamSubscription&lt;/code&gt; and cancel it in &lt;code&gt;dispose()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Widget renders blank&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ctx.data&lt;/code&gt; type mismatch&lt;/td&gt;
&lt;td&gt;Cast with explicit null fallback: &lt;code&gt;ctx.data as Map&amp;lt;String, Object?&amp;gt;? ?? {}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout overflow&lt;/td&gt;
&lt;td&gt;Custom widget using &lt;code&gt;Expanded&lt;/code&gt; or unbounded height&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;mainAxisSize: MainAxisSize.min&lt;/code&gt; on all &lt;code&gt;Column&lt;/code&gt;/&lt;code&gt;Row&lt;/code&gt; widgets inside catalog items&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Kalaam as a Production Reference
&lt;/h2&gt;

&lt;p&gt;Everything covered in this tutorial appears at production scale in &lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;Kalaam · كلام&lt;/a&gt;. Here is the map:&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;Kalaam location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;13 custom &lt;code&gt;CatalogItem&lt;/code&gt;s&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/catalog/items/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined catalog (custom + primitives)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/catalog/catalog.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SurfaceController&lt;/code&gt; + &lt;code&gt;Conversation&lt;/code&gt; wiring&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/shared/services/ai_session_service.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;A2uiTransportAdapter&lt;/code&gt; with logging tap&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/shared/services/ai_session_service.dart:61&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live GenUI Inspector&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/view/widgets/genui_inspector.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;UserActionEvent&lt;/code&gt; helper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/catalog/kalaam_actions.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataModel binding with &lt;code&gt;BoundBool&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/catalog/items/root_explorer_item.dart:295&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System prompt with pedagogy fragments&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/prompt/kalaam_prompt.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Demo Mode (no credentials)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lib/features/session/demo/kalaam_demo.dart&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture overview&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgvox5bktyc3thny2hc5i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgvox5bktyc3thny2hc5i.png" alt="Kalaam widget catalog — VocabCarousel showing full Arabic diacritics with IPA and English" width="285" height="620"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One of Kalaam's 13 custom &lt;code&gt;CatalogItem&lt;/code&gt;s: the &lt;code&gt;VocabCarousel&lt;/code&gt;. Gemini picks which words go in the deck, the widget renders them with full diacritics, transliteration, and example sentences.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Clone it, run it in Demo Mode (&lt;code&gt;flutter run --dart-define=KALAAM_DEMO=true&lt;/code&gt;), and open the Live GenUI Inspector. Watch the A2UI JSON stream as you interact with the lesson. That transparency is intentional — Kalaam was built to make the GenUI programming model concrete.&lt;/p&gt;


&lt;h2&gt;
  
  
  What's Next: genui_catalog and the Broader A2UI Ecosystem
&lt;/h2&gt;

&lt;p&gt;Once you have built your first custom &lt;code&gt;CatalogItem&lt;/code&gt;, you may not need to build everything from scratch. The &lt;code&gt;genui_catalog&lt;/code&gt; package (version 0.3.0) ships 17 pre-built production components:&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="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;genui_catalog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.3.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Highlights: &lt;code&gt;KpiCard&lt;/code&gt;, &lt;code&gt;DataTable&lt;/code&gt;, &lt;code&gt;TimelineCard&lt;/code&gt;, &lt;code&gt;ActionForm&lt;/code&gt;, &lt;code&gt;ProfileCard&lt;/code&gt;, &lt;code&gt;StepperCard&lt;/code&gt;, &lt;code&gt;SearchBar&lt;/code&gt;, &lt;code&gt;ChartCard&lt;/code&gt;. For dashboards, data apps, or form-heavy interfaces, this package saves significant time. For domain-specific apps like Kalaam, custom &lt;code&gt;CatalogItem&lt;/code&gt;s are still irreplaceable — no generic primitive approximates a radial Arabic root diagram.&lt;/p&gt;

&lt;p&gt;The A2UI protocol itself has ecosystem participants beyond Flutter. React, Angular, Lit, and several agent frameworks (AG2, Vercel json-renderer) implement the same wire format. A Flutter app using &lt;code&gt;genui&lt;/code&gt; can talk to a Node.js backend agent using the same A2UI v0.9 protocol via the &lt;code&gt;genui_a2a&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;For further reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.flutter.dev/ai/genui" rel="noopener noreferrer"&gt;Official Flutter GenUI docs&lt;/a&gt; — the authoritative API reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/genui-intro" rel="noopener noreferrer"&gt;Google Codelabs: Build a GenUI App&lt;/a&gt; — interactive guided walkthrough&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;Kalaam on GitHub&lt;/a&gt; — the production reference used throughout this tutorial, Apache-2.0&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What You Have Built
&lt;/h2&gt;

&lt;p&gt;GenUI inverts the traditional app architecture. Instead of shipping a fixed screen for every state, you ship a vocabulary of widgets and let the model decide how to compose them. The Flutter GenUI SDK gives you the scaffolding to make that work: &lt;code&gt;CatalogItem&lt;/code&gt; to define your vocabulary, &lt;code&gt;Catalog&lt;/code&gt; to group it, &lt;code&gt;SurfaceController&lt;/code&gt; as the runtime engine, &lt;code&gt;A2uiTransportAdapter&lt;/code&gt; as the streaming bridge, and &lt;code&gt;Conversation&lt;/code&gt; as the facade that ties it all together.&lt;/p&gt;

&lt;p&gt;The A2UI protocol underneath is simple: four message types, a reactive data model, and a streaming JSON parser. Once you see those four messages — &lt;code&gt;createSurface&lt;/code&gt;, &lt;code&gt;surfaceUpdate&lt;/code&gt;, &lt;code&gt;dataModelUpdate&lt;/code&gt;, &lt;code&gt;deleteSurface&lt;/code&gt; — the entire SDK is predictable.&lt;/p&gt;

&lt;p&gt;Kalaam applies this architecture to Arabic language learning with 13 custom teaching widgets, a bidirectional interaction loop, live DataModel binding for learner progress, and a transparent inspector so you can watch the model work. It is open source, fully documented, and built specifically to be the kind of reference this tutorial needed.&lt;/p&gt;

&lt;p&gt;The next time someone asks what "AI-native" means in a mobile app, you have a concrete answer — and working code to show them.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>beginners</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Built Kalaam: An Arabic Tutor Whose UI Gemini Assembles at Runtime</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Thu, 18 Jun 2026 15:46:02 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/i-built-kalaam-an-arabic-tutor-whose-ui-gemini-assembles-at-runtime-1893</link>
      <guid>https://dev.to/sayed_ali_alkamel/i-built-kalaam-an-arabic-tutor-whose-ui-gemini-assembles-at-runtime-1893</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it is&lt;/strong&gt;: Kalaam is an open-source Flutter app where a Gemini model composes each Arabic lesson screen at runtime from a combined catalog of 13 custom Arabic widgets and 14 genui SDK primitives. No fixed screens. No hardcoded lesson flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who it is for&lt;/strong&gt;: Flutter developers building AI-native apps who want a real, runnable reference implementation, not another chatbot wrapper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it now&lt;/strong&gt; (no Firebase, no API key, ~30 seconds):&lt;/li&gt;
&lt;/ul&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;  git clone https://github.com/sayed3li97/kalaam.git
  &lt;span class="nb"&gt;cd &lt;/span&gt;kalaam &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; flutter pub get
  dart run build_runner build &lt;span class="nt"&gt;--delete-conflicting-outputs&lt;/span&gt;
  flutter run &lt;span class="nt"&gt;--dart-define&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;KALAAM_DEMO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full source&lt;/strong&gt;: &lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;https://github.com/sayed3li97/kalaam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maturity&lt;/strong&gt;: Alpha. Demo Mode is stable. Live Mode (Gemini calling your Firebase project in real time) requires a setup step documented in the README.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What Problem This Actually Solves&lt;/li&gt;
&lt;li&gt;What Kalaam Is&lt;/li&gt;
&lt;li&gt;Key Features in Depth&lt;/li&gt;
&lt;li&gt;How Kalaam Compares&lt;/li&gt;
&lt;li&gt;The Honest Objection&lt;/li&gt;
&lt;li&gt;Getting Started in Under 5 Minutes&lt;/li&gt;
&lt;li&gt;How to Contribute&lt;/li&gt;
&lt;li&gt;What This Means for Flutter Development&lt;/li&gt;
&lt;li&gt;Questions developers are actually asking about Kalaam&lt;/li&gt;
&lt;li&gt;What Comes Next&lt;/li&gt;
&lt;li&gt;The Catalog Is the New Source of Truth&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;About the Author&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Arabic morphology operates on a principle with no real equivalent in English. Almost every word in the language descends from a three-letter root through predictable morphological patterns. The root ك-ت-ب (k-t-b, the concept of writing) produces كَتَبَ (he wrote), كِتَاب (book), كَاتِب (writer), مَكْتَب (office), مَكْتُوب (written). Classical Arab grammarians built complete transformation tables for these patterns. Modern linguists write dissertations about them. The system is elegant, generative, and genuinely hard to teach with a flashcard.&lt;/p&gt;

&lt;p&gt;Every Arabic learning app I have reviewed responds to this complexity the same way: fixed screens, hardcoded vocabulary lists, a multiple-choice grid that never changes shape, and a next button the AI model has no opinion about. The model, when it appears at all, generates text that lands in a &lt;code&gt;Text&lt;/code&gt; widget inside a layout the developer wired months before knowing what the learner would need. The interface is not part of what the model controls.&lt;/p&gt;

&lt;p&gt;Kalaam takes a different position. Rather than asking a language model to fill in a predetermined form, Kalaam gives Gemini a catalog of real Flutter widgets and lets it decide what to compose. The model reads a JSON schema describing 13 custom Arabic teaching widgets plus 14 genui built-in primitives, then streams an A2UI surface that the GenUI SDK materializes into an actual widget tree on the device. Tap a word node in the root diagram, and the interaction goes back to Gemini as a &lt;code&gt;UserActionEvent&lt;/code&gt;. The model then writes whatever surface comes next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Problem This Actually Solves
&lt;/h2&gt;

&lt;p&gt;The standard architecture for AI-assisted Flutter apps produces code that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What most "AI-powered" learning apps actually do&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Text&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="na"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model generates text. A fixed UI the developer designed wraps it. The model has no knowledge of what a conjugation table looks like, cannot decide to show a vocabulary carousel instead of a fill-in-the-blank when the learner already knows the words, and cannot react to a tapped word by composing a triliteral root diagram for that specific word. Every state transition was decided at compile time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The architectural gap&lt;/strong&gt;: A model that only fills &lt;code&gt;String&lt;/code&gt;s into fixed widgets can generate better content. It cannot generate better teaching. Those are different things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://pub.dev/packages/genui" rel="noopener noreferrer"&gt;genui&lt;/a&gt; Flutter package (A2UI v0.9) addresses this by giving the model a catalog schema and a structured transport protocol. The model emits JSON messages like &lt;code&gt;createSurface&lt;/code&gt; and &lt;code&gt;updateComponents&lt;/code&gt; that describe a widget tree using catalog item names. The SDK parses those messages and calls each widget's registered &lt;code&gt;widgetBuilder&lt;/code&gt;. The developer defines the catalog. The model decides which items to use, with what data, and in what order.&lt;/p&gt;

&lt;p&gt;Kalaam is a complete, open-source implementation of this pattern for Arabic instruction, with 13 custom &lt;code&gt;CatalogItem&lt;/code&gt; widgets covering morphology, vocabulary, phonetics, dialogue, cultural context, and progress tracking. The full source is at &lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;https://github.com/sayed3li97/kalaam&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Kalaam Is
&lt;/h2&gt;

&lt;p&gt;Kalaam is a Flutter application where Gemini assembles each Arabic lesson interface at runtime. Pick a scenario (Ordering Coffee, a Cairo market negotiation, a formal business letter), and the model composes a lesson surface: a scene-setter first, then vocabulary cards, a triliteral root diagram, a conjugation table, a quiz, depending on what the learner does and how they answer. The widget sequence is not scripted. The model chooses it.&lt;/p&gt;

&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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Fdemo.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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Fdemo.png" alt="Kalaam home screen — scenario picker with streak badges" width="285" height="620"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Froot_explorer.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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Froot_explorer.png" alt="Root System diagram — Arabic triliteral root ش-ر-ب with radial word family" width="390" height="848"&gt;&lt;/a&gt;&lt;br&gt;
&amp;nbsp;&amp;nbsp;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Froot_expanded.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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Froot_expanded.png" alt="Root node expanded — wazn pattern badge and Explore button" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scenario picker &amp;nbsp;·&amp;nbsp; Root System (ش-ر-ب) &amp;nbsp;·&amp;nbsp; Tap a node to reveal its morphological pattern — every screen composed live by Gemini.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I (Sayed Ali Alkamel, &lt;a href="https://github.com/sayed3li97" rel="noopener noreferrer"&gt;@sayed3li97&lt;/a&gt;) made one architectural decision early that shaped everything else: representing the entire model-facing contract as typed Dart &lt;code&gt;CatalogItem&lt;/code&gt; objects rather than a YAML configuration file or a JSON registry. The &lt;code&gt;dataSchema&lt;/code&gt; and &lt;code&gt;widgetBuilder&lt;/code&gt; on each item live in the same Dart file. The JSON schema the model learns and the Flutter code that renders it are co-located. If you change the schema, the compiler tells you if the widget no longer matches it.&lt;/p&gt;

&lt;p&gt;Install and run in Demo Mode (Flutter 3.44+, no Firebase, no API key needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sayed3li97/kalaam.git
&lt;span class="nb"&gt;cd &lt;/span&gt;kalaam
flutter pub get
dart run build_runner build &lt;span class="nt"&gt;--delete-conflicting-outputs&lt;/span&gt;
flutter run &lt;span class="nt"&gt;--dart-define&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;KALAAM_DEMO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core primitive is &lt;code&gt;CatalogItem&lt;/code&gt;. Every custom widget Kalaam exposes to the model is registered as one. Here is the RootExplorer registration, simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;rootExplorerItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CatalogItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;name:&lt;/span&gt; &lt;span class="s"&gt;'RootExplorer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;dataSchema:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;'rootWord'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Triliteral root, letters joined by dashes, e.g. ك-ت-ب'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'rootMeaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Core meaning of the root in English'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;'family'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Derived words that share this root'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;items:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;properties:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s"&gt;'word'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Derived word with full harakat'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="s"&gt;'transliteration'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Romanised pronunciation'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'English meaning'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="s"&gt;'pattern'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'Morphological wazn, e.g. مَفْعُول'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="s"&gt;'isExpanded'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;description:&lt;/span&gt; &lt;span class="s"&gt;'DataModel-bound, false initially'&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;span class="kd"&gt;required&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'rootWord'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'rootMeaning'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'family'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nl"&gt;widgetBuilder:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_RootExplorerWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;rootWord:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'rootWord'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;family:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'family'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="nl"&gt;ctx:&lt;/span&gt; &lt;span class="n"&gt;ctx&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;When Gemini wants to teach the ش-ر-ب root family, it emits a &lt;code&gt;createSurface&lt;/code&gt; message containing a &lt;code&gt;RootExplorer&lt;/code&gt; component with the root letters, meaning, and word family filled in. The GenUI SDK calls &lt;code&gt;widgetBuilder&lt;/code&gt; and returns a live Flutter widget. The model never saw the Flutter code. It only knew the JSON schema.&lt;/p&gt;

&lt;p&gt;Under the hood, a &lt;code&gt;SurfaceController&lt;/code&gt; manages the bidirectional loop. Incoming A2UI messages build the current surface. Outgoing &lt;code&gt;UserActionEvent&lt;/code&gt; payloads carry learner interactions back into the model's conversation history, closing the loop for the next turn.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Features in Depth
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Root System Explorer
&lt;/h3&gt;

&lt;p&gt;The RootExplorer renders a radial diagram centered on a triliteral root. Each satellite node is a derived word in the root family. Tapping a node expands it from a 92×46pt pill showing just the Arabic word to a 155×144pt card revealing the morphological pattern (وزن / wazn), the transliteration and English meaning, and an "Explore" button.&lt;/p&gt;

&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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Froot_expanded.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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Froot_expanded.png" alt="Root Explorer — مَشْرُوب node expanded with وزن مَفْعُول badge and Explore button" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tapping مَشْرُوب (a drink) expands the node to reveal its wazn (وزن مَفْعُول), transliteration, English meaning, and the Explore→ button that branches into a new Gemini-composed surface.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That Explore button is not cosmetic. It dispatches a &lt;code&gt;UserActionEvent&lt;/code&gt; back to Gemini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;sendKalaamAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'explore_word'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s"&gt;'word'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;'root'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rootWord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;'meaning'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;meaning&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;Gemini receives this in the next conversation turn and can compose an entirely new surface for that specific word: a &lt;code&gt;VocabCard&lt;/code&gt; with its etymology, a &lt;code&gt;ConjugationTable&lt;/code&gt; if it is a verb, a &lt;code&gt;CulturalNote&lt;/code&gt; if the word has idiomatic significance. The diagram is not a static illustration. It is a branch point in the lesson.&lt;/p&gt;

&lt;h3&gt;
  
  
  HarakatBuilder
&lt;/h3&gt;

&lt;p&gt;Arabic diacritics (harakat) are one of the most common barriers for intermediate learners, and also one of the hardest to teach with a flashcard. Most learning apps just display fully voweled text. HarakatBuilder inverts the exercise.&lt;/p&gt;

&lt;p&gt;Given a consonant skeleton and a target vocalization, the learner taps each letter and selects a diacritic from a palette: fatha (َ), kasra (ِ), damma (ُ), sukun (ْ), shadda (ّ). The word re-renders with each placed diacritic in real time. On correct completion, the widget dispatches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;sendKalaamAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'isCorrect'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model receives that and composes the next step based on what the learner just demonstrated they know. No hardcoded branching logic in the Flutter code. The model decides where to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live GenUI Inspector
&lt;/h3&gt;

&lt;p&gt;The Inspector is the piece of Kalaam that surprises developers most on first run.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;{}&lt;/code&gt; button in the session screen's app bar opens a slide-up panel that shows every A2UI message Gemini has emitted during the current session, newest last, pretty-printed as JSON. You can watch &lt;code&gt;createSurface&lt;/code&gt; messages appear as the lesson loads. You can see the component names, the data the model chose to fill in, the surface IDs. You can trace exactly what JSON produced exactly what widget on screen.&lt;/p&gt;

&lt;p&gt;When I first built this, I left the Inspector open for every demo I ran internally. Watching &lt;code&gt;"component": "RootExplorer"&lt;/code&gt; appear in the stream, followed by a complete Arabic root family the model composed from nothing but the schema description, is the clearest explanation of what GenUI actually does. No architecture diagram I have drawn since has communicated it as well as this panel does in practice.&lt;/p&gt;

&lt;p&gt;The Inspector is implemented as a &lt;code&gt;ValueNotifier&amp;lt;List&amp;lt;String&amp;gt;&amp;gt;&lt;/code&gt; buffer on the transport layer, with a slide-up panel widget that rebuilds as messages arrive. It is not a debug-only feature: it stays in release builds because it is the most compelling demonstration of the pattern.&lt;/p&gt;

&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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Finspector.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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Finspector.png" alt="Live GenUI Inspector panel showing CREATE turn_2 A2UI JSON from Gemini" width="390" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The GenUI Inspector open mid-session. The &lt;code&gt;CREATE turn_2&lt;/code&gt; badge marks a new surface creation message. Every field name and value was chosen by Gemini — no developer-written template produced this JSON.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo Mode
&lt;/h3&gt;

&lt;p&gt;Live Mode requires a Firebase project with Gemini Developer API enabled. That setup step has a real cost for someone evaluating the project for the first time. Demo Mode removes it.&lt;/p&gt;

&lt;p&gt;Run with &lt;code&gt;--dart-define=KALAAM_DEMO=true&lt;/code&gt; and the app replays curated A2UI transcripts through the same &lt;code&gt;A2uiTransportAdapter&lt;/code&gt; used in Live Mode. Nothing is mocked at the widget level. &lt;code&gt;SurfaceController&lt;/code&gt; processes the same message types. &lt;code&gt;CatalogItem&lt;/code&gt; &lt;code&gt;widgetBuilder&lt;/code&gt; functions instantiate the same Flutter widgets. The difference is only the source of the A2UI stream: a scripted file instead of a live Gemini response.&lt;/p&gt;

&lt;p&gt;This was not an accidental boundary. I put the swap at the transport layer deliberately so that Demo Mode exercises the real rendering pipeline. A bug in the catalog, a schema mismatch, an overflow in the Root Explorer: Demo Mode will surface all of them. It is not a polished preview mode. It is the actual system, running on prerecorded input.&lt;/p&gt;

&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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Fvocab_carousel.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%2Fraw.githubusercontent.com%2Fsayed3li97%2Fkalaam%2Fmain%2Fassets%2Fscreenshots%2Fvocab_carousel.png" alt="VocabCarousel widget — Arabic word قَهْوَة with full harakat, transliteration, and definition" width="285" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VocabCarousel showing قَهْوَة (coffee) with full diacritics, IPA, and example sentence — rendered from a Gemini-emitted &lt;code&gt;VocabCarousel&lt;/code&gt; component in a Demo Mode replay.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Combined Catalog
&lt;/h3&gt;

&lt;p&gt;Kalaam's combined catalog gives the model 27 composable elements: 13 custom Arabic widgets plus 14 genui built-in primitives.&lt;/p&gt;

&lt;p&gt;The 13 custom items: &lt;code&gt;RootExplorer&lt;/code&gt;, &lt;code&gt;HarakatBuilder&lt;/code&gt;, &lt;code&gt;ConjugationTable&lt;/code&gt;, &lt;code&gt;VocabCard&lt;/code&gt;, &lt;code&gt;VocabCarousel&lt;/code&gt;, &lt;code&gt;SceneCard&lt;/code&gt;, &lt;code&gt;PhonemeCard&lt;/code&gt;, &lt;code&gt;DialogueBubble&lt;/code&gt;, &lt;code&gt;CulturalNote&lt;/code&gt;, &lt;code&gt;MasteryRing&lt;/code&gt;, &lt;code&gt;QuickChoice&lt;/code&gt;, &lt;code&gt;SentenceBuilder&lt;/code&gt;, &lt;code&gt;FillInTheBlank&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The 14 genui primitives: &lt;code&gt;Column&lt;/code&gt;, &lt;code&gt;Row&lt;/code&gt;, &lt;code&gt;Card&lt;/code&gt;, &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;Icon&lt;/code&gt;, &lt;code&gt;Divider&lt;/code&gt;, &lt;code&gt;Image&lt;/code&gt;, &lt;code&gt;ChoicePicker&lt;/code&gt;, &lt;code&gt;TextField&lt;/code&gt;, &lt;code&gt;Slider&lt;/code&gt;, &lt;code&gt;Tabs&lt;/code&gt;, &lt;code&gt;List&lt;/code&gt;, &lt;code&gt;AudioPlayer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model can compose both tiers freely. A &lt;code&gt;Column&lt;/code&gt; containing a &lt;code&gt;SceneCard&lt;/code&gt;, then a &lt;code&gt;VocabCarousel&lt;/code&gt;, then a &lt;code&gt;QuickChoice&lt;/code&gt; quiz is a perfectly valid A2UI surface. In practice, across Live Mode sessions I have run, the model reaches for mixed-tier layouts frequently: a &lt;code&gt;Card&lt;/code&gt; primitive wrapping a custom &lt;code&gt;DialogueBubble&lt;/code&gt; for roleplay, a &lt;code&gt;Row&lt;/code&gt; of &lt;code&gt;Button&lt;/code&gt; primitives alongside a &lt;code&gt;MasteryRing&lt;/code&gt;. That mixing is where the showcase value sits.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Kalaam Compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Model composes the layout&lt;/th&gt;
&lt;th&gt;Bidirectional events&lt;/th&gt;
&lt;th&gt;No-API-key demo&lt;/th&gt;
&lt;th&gt;Custom widget catalog&lt;/th&gt;
&lt;th&gt;Time to first running UI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kalaam + GenUI SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (UserActionEvent)&lt;/td&gt;
&lt;td&gt;Yes (Demo Mode)&lt;/td&gt;
&lt;td&gt;27 items (13 custom + 14 primitives)&lt;/td&gt;
&lt;td&gt;~30 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DIY JSON-to-widget mapper&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;td&gt;Depends&lt;/td&gt;
&lt;td&gt;You build it from scratch&lt;/td&gt;
&lt;td&gt;Weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flutter_ai_toolkit&lt;/td&gt;
&lt;td&gt;No (fixed chat UI)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;~1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM + &lt;code&gt;Text()&lt;/code&gt; widget&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;~10 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firebase Genkit + custom Flutter&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;td&gt;No (requires Firebase)&lt;/td&gt;
&lt;td&gt;You build it from scratch&lt;/td&gt;
&lt;td&gt;Days to weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;flutter_ai_toolkit&lt;/code&gt; wins on time-to-working-chat: if you need a conversational UI embedded in an existing Flutter app, it is the fastest path by a significant margin. Use it. [INTERNAL LINK: flutter_ai_toolkit]&lt;/p&gt;

&lt;p&gt;If you need the model to control the layout itself and not just the content inside a fixed layout, the DIY approach and Kalaam/GenUI are the two real options. Kalaam exists as a reference for what "DIY done well" looks like at meaningful scale, so you can evaluate whether using the SDK directly fits your use case before committing to building from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Objection
&lt;/h2&gt;

&lt;p&gt;A senior Flutter developer who clones Kalaam and runs it in Demo Mode will ask one question almost immediately: "How much of this is Gemini and how much is a scripted replay?"&lt;/p&gt;

&lt;p&gt;It is a fair question. Demo Mode replays prewritten A2UI transcripts. The Root Explorer diagram you see when you run &lt;code&gt;flutter run --dart-define=KALAAM_DEMO=true&lt;/code&gt; was not composed by Gemini in real time. It was composed during development, reviewed, committed to the repo, and is being replayed now. The model is not in the loop in Demo Mode.&lt;/p&gt;

&lt;p&gt;That is not a misleading presentation, but it matters for evaluating the system. Demo Mode does not show you how Gemini handles unexpected learner input. It does not show what happens when the model emits a structurally valid but semantically off A2UI message (the GenUI SDK renders a graceful error card). It does not show live response latency.&lt;/p&gt;

&lt;p&gt;Live Mode is where those questions get real answers. It also requires real setup: a Firebase project, Gemini Developer API enabled, Firebase App Check configured, and a billing budget alert set. The README's "Option B" section walks through all of it.&lt;/p&gt;

&lt;p&gt;The limitation I want to name clearly: Kalaam's Live Mode prompt engineering is functional but not complete. In live sessions, Gemini occasionally reaches for a &lt;code&gt;VocabCard&lt;/code&gt; where a &lt;code&gt;RootExplorer&lt;/code&gt; would have been more instructive, or composes a layout that works but underuses the available screen space. The catalog gives the model the right tools. Teaching it when to reach for each one is an ongoing prompt engineering problem with no fully correct solution yet.&lt;/p&gt;

&lt;p&gt;This is alpha software. The architecture is the part that works well. The prompt, the few-shot examples, and the handling of edge-case model outputs are the areas where contributions move the project most.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started in Under 5 Minutes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;: Flutter 3.44+, Dart 3.9+, a connected device or simulator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Clone and install dependencies&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sayed3li97/kalaam.git
&lt;span class="nb"&gt;cd &lt;/span&gt;kalaam
flutter pub get
dart run build_runner build &lt;span class="nt"&gt;--delete-conflicting-outputs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Run in Demo Mode&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run &lt;span class="nt"&gt;--dart-define&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;KALAAM_DEMO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Firebase project. No Gemini API key. No account required. The demo replays an Ordering Coffee lesson through the real GenUI pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Open the Inspector&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once a lesson starts, tap the &lt;code&gt;{}&lt;/code&gt; button in the top-right of the session screen. The GenUI Inspector panel opens and shows every A2UI message the surface was built from. This is where the architecture becomes visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 (optional): Switch to Live Mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a Firebase project at &lt;a href="https://console.firebase.google.com" rel="noopener noreferrer"&gt;console.firebase.google.com&lt;/a&gt;. Enable Build → AI Logic → Get started → Gemini Developer API (free tier available). Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dart pub global activate flutterfire_cli
flutterfire configure    &lt;span class="c"&gt;# generates lib/firebase_options.dart, which is git-ignored&lt;/span&gt;
flutter run              &lt;span class="c"&gt;# no --dart-define flag; live mode is the default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a billing budget alert before running live sessions. The Gemini Developer API bills your Firebase project, and &lt;code&gt;firebase_options.dart&lt;/code&gt; contains credentials that should never be committed to a public repository. The &lt;code&gt;.gitignore&lt;/code&gt; in Kalaam already excludes it, along with &lt;code&gt;google-services.json&lt;/code&gt; and &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;. Only &lt;code&gt;*.example&lt;/code&gt; templates are tracked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Known open issues&lt;/strong&gt; (as of the initial open-source release):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Occasional layout overflow on very long Arabic words in &lt;code&gt;VocabCard&lt;/code&gt; on narrow screens.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConjugationTable&lt;/code&gt; scroll behavior on small-screen Android devices.&lt;/li&gt;
&lt;li&gt;Live Mode system prompt does not yet handle learner dialect preference (MSA only).&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Kalaam is open source under Apache 2.0 and genuinely looking for people to use it, break it, and tell me what they found.&lt;/p&gt;

&lt;p&gt;Running Demo Mode and filing a GitHub issue when something looks wrong, visually off, or surprising is a real contribution. You do not need to write code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where contributions have the highest impact right now:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt refinement&lt;/strong&gt; is the single highest-leverage area. The system prompt lives in &lt;code&gt;lib/features/session/prompt/&lt;/code&gt;. If you have experience with Arabic pedagogy or prompt engineering for Gemini, adding one well-structured example to the few-shots can change what the model reaches for across an entire session. The current few-shot coverage is thin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New catalog items&lt;/strong&gt; follow a clear pattern. The reference implementation is &lt;code&gt;lib/features/session/catalog/items/scene_card_item.dart&lt;/code&gt;. Every &lt;code&gt;CatalogItem&lt;/code&gt; needs a &lt;code&gt;dataSchema&lt;/code&gt;, &lt;code&gt;exampleData&lt;/code&gt;, and a &lt;code&gt;widgetBuilder&lt;/code&gt;. Register it in &lt;code&gt;catalog.dart&lt;/code&gt; and add a fixture in &lt;code&gt;test/kalaam_catalog_test.dart&lt;/code&gt;. The test infrastructure is already there; you just fill in the new widget's test case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform testing&lt;/strong&gt; is something I cannot do alone. Kalaam has CI for iOS, Android, and web. Arabic text rendering behavior varies across platform versions and font configurations in ways that are hard to predict. If you run into something that looks wrong on a specific device or OS version, filing an issue with a screenshot is directly useful.&lt;/p&gt;

&lt;p&gt;Good first issues are labeled on GitHub. They are scoped to single files and documented with enough context to start without knowing the whole codebase.&lt;/p&gt;

&lt;p&gt;Full process: &lt;a href="https://github.com/sayed3li97/kalaam/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Flutter Development
&lt;/h2&gt;

&lt;p&gt;Three practical framings depending on where you are right now:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are currently building AI features by wiring an LLM to a &lt;code&gt;Text&lt;/code&gt; widget&lt;/strong&gt;, Kalaam is a working example of what the next step looks like architecturally. The migration is not trivial (you need to define a catalog, think in surfaces rather than screens, and design the bidirectional event contract), but the reference implementation now exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are evaluating GenUI for production use&lt;/strong&gt;, Kalaam gives you 13 &lt;code&gt;CatalogItem&lt;/code&gt; implementations to read and adapt. The bidirectional event system in &lt;code&gt;sendKalaamAction&lt;/code&gt;, the data model binding in &lt;code&gt;BoundBool&lt;/code&gt; and &lt;code&gt;BoundString&lt;/code&gt;, the Inspector widget, and the Demo Mode scaffold are all things you can extract and use in your own project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are teaching Flutter architecture or developer experience&lt;/strong&gt;, the Inspector is a classroom tool worth having. Watching A2UI JSON tokens resolve into typed Flutter widgets in real time, in an app you cloned ten minutes ago, communicates what "model-driven UI" actually means better than any diagram I know.&lt;/p&gt;

&lt;p&gt;One concrete recommendation before building a custom JSON-to-widget mapper for your own AI-native Flutter project: read the &lt;a href="https://pub.dev/packages/genui" rel="noopener noreferrer"&gt;genui&lt;/a&gt; package README and the A2UI v0.9 specification alongside Kalaam's catalog. The scaffold may be closer to complete than you expect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Questions developers are actually asking about Kalaam
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Kalaam production-ready as an Arabic learning app?
&lt;/h3&gt;

&lt;p&gt;No. Kalaam is an open-source showcase application in alpha, designed to demonstrate GenUI architecture patterns for Flutter developers. The widget catalog is functional, the Demo Mode is stable, and the GenUI bidirectional loop works end-to-end. The Arabic pedagogy, prompt engineering, and learner progress tracking are not at production quality. If you are building a production Arabic learning product, Kalaam's catalog and architecture are worth studying and adapting, but the app itself is a starting point, not a finished product.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between Kalaam's Demo Mode and Live Mode?
&lt;/h3&gt;

&lt;p&gt;Demo Mode (&lt;code&gt;--dart-define=KALAAM_DEMO=true&lt;/code&gt;) replays prerecorded A2UI transcripts through the real GenUI pipeline. No API key or Firebase project is needed. Live Mode calls Gemini through Firebase AI Logic in real time, composing each lesson surface fresh based on the learner's actions and conversation history. Demo Mode is fully stable and exercises the same rendering code as Live Mode. Live Mode is functional but requires Firebase setup and is billed through your Firebase project's Gemini Developer API quota.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Kalaam require a paid Gemini API key?
&lt;/h3&gt;

&lt;p&gt;In Demo Mode, no. In Live Mode, Kalaam uses Firebase AI Logic with the Gemini Developer API, which has a free tier but can incur charges at scale. The README recommends setting a Firebase billing budget alert before enabling Live Mode. Firebase App Check is also required to prevent unauthorized API usage, since the Gemini Developer API endpoint bills your project and an unprotected key is abusable.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Kalaam's approach differ from using flutter_ai_toolkit?
&lt;/h3&gt;

&lt;p&gt;The flutter_ai_toolkit (Google) embeds a ready-made chat UI component into an existing Flutter app. The model generates text responses inside a conversation interface the toolkit provides. Kalaam's GenUI approach is architecturally different: the model decides what Flutter widgets to render, which ones to compose together, and how to lay them out. The developer provides a widget catalog; the model chooses from it at runtime and updates surfaces in place. These are tools for different problems, not competing approaches to the same one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add my own widgets to Kalaam's catalog?
&lt;/h3&gt;

&lt;p&gt;Yes. Every item in the catalog is a &lt;code&gt;CatalogItem&lt;/code&gt; with three required fields: a &lt;code&gt;dataSchema&lt;/code&gt; (the JSON Schema object the model uses to understand what data the widget accepts), &lt;code&gt;exampleData&lt;/code&gt; (a worked example the system prompt includes as a few-shot), and a &lt;code&gt;widgetBuilder&lt;/code&gt; (a function taking &lt;code&gt;CatalogItemContext&lt;/code&gt; and returning a Flutter &lt;code&gt;Widget&lt;/code&gt;). The reference implementation is &lt;code&gt;lib/features/session/catalog/items/scene_card_item.dart&lt;/code&gt;. Register your item in &lt;code&gt;lib/features/session/catalog/catalog.dart&lt;/code&gt; and add a fixture test in &lt;code&gt;test/kalaam_catalog_test.dart&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What version of Flutter does Kalaam require, and why?
&lt;/h3&gt;

&lt;p&gt;Flutter 3.44+ and Dart 3.9+. The lower bound comes from the &lt;code&gt;genui&lt;/code&gt; package's dependency requirements and Kalaam's use of Dart 3 patterns: &lt;code&gt;@riverpod&lt;/code&gt; codegen, &lt;code&gt;@freezed&lt;/code&gt; immutable models, and sealed classes. The codegen step (&lt;code&gt;dart run build_runner build&lt;/code&gt;) must complete before running the app; generated files are not committed to the repository.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;The roadmap items below are drawn from open GitHub issues and gaps I identified during development. None have committed dates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt few-shot expansion&lt;/strong&gt; is the highest-priority item. The current system prompt covers the Ordering Coffee scenario in detail. More worked examples from different lesson contexts (market negotiation, formal letter writing, phonetics drill) would each give the model a better map for when to reach for each catalog widget. A single well-structured example in the few-shots has outsized effect on Live Mode output quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learner profile binding&lt;/strong&gt; is the next feature in the data model. The &lt;code&gt;/learner/&lt;/code&gt; path in the DataModel already exists (tracking words seen, accuracy, weak phonemes, streak). The model reads it at session start. The missing piece is writing it after each completed interaction and binding &lt;code&gt;MasteryRing&lt;/code&gt; to real accumulated progress rather than per-session state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dialect support&lt;/strong&gt; is on the wishlist. Modern Standard Arabic is the current default. A dialect selector that modifies the system prompt would let the app serve Gulf Arabic, Egyptian Arabic, and Levantine Arabic learners, each of which has distinct vocabulary and morphological patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better malformed A2UI handling&lt;/strong&gt; is a known gap. When Gemini emits a structurally valid but semantically incorrect A2UI message (wrong component name, missing required field), the current behavior is a generic error card. Partial rendering and schema-level error recovery would make Live Mode more resilient.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Catalog Is the New Source of Truth
&lt;/h2&gt;

&lt;p&gt;There is a pattern in software where the interface between two systems starts as an implementation detail and gradually becomes the most load-bearing thing in the architecture. REST APIs replaced direct database calls. GraphQL schemas replaced hand-written REST endpoints. Type systems replaced runtime duck typing. In each case, the contract became the artifact worth designing carefully.&lt;/p&gt;

&lt;p&gt;Kalaam's &lt;code&gt;CatalogItem&lt;/code&gt; plays this role between a language model and a Flutter widget tree. The &lt;code&gt;dataSchema&lt;/code&gt; property is what the model learns. It is the description Gemini uses to decide when to emit a &lt;code&gt;RootExplorer&lt;/code&gt; versus a &lt;code&gt;ConjugationTable&lt;/code&gt;, what fields to fill in, what data shapes are valid. The schema is how you teach a model what your widgets can do.&lt;/p&gt;

&lt;p&gt;The practical consequence: time spent making catalog schemas precise pays forward across every session where the model uses that widget. A vague &lt;code&gt;description&lt;/code&gt; field on a schema property produces vague model output. A specific description, one that names the format and gives a concrete example, produces surfaces that feel like they were composed for the learner's exact situation, because the model had enough information to make a real choice.&lt;/p&gt;

&lt;p&gt;I built Kalaam to see what Arabic instruction looks like when the interface is part of what the model controls. The answer: it looks different every session, reaches for a triliteral root diagram when that is the right tool, and reacts to a learner tapping a specific word in ways a static screen cannot. That sounds obvious in retrospect. It is harder to build correctly than it appears.&lt;/p&gt;

&lt;p&gt;The source is at &lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;https://github.com/sayed3li97/kalaam&lt;/a&gt;. Run it, open the Inspector, tap a word in the root diagram, and watch what the model composes next. Something in there is worth knowing about.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Kalaam GitHub repository: &lt;a href="https://github.com/sayed3li97/kalaam" rel="noopener noreferrer"&gt;https://github.com/sayed3li97/kalaam&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;genui Flutter package (pub.dev): &lt;a href="https://pub.dev/packages/genui" rel="noopener noreferrer"&gt;https://pub.dev/packages/genui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A2UI v0.9 protocol (Google): &lt;a href="https://github.com/google/A2UI" rel="noopener noreferrer"&gt;https://github.com/google/A2UI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Firebase AI Logic (Gemini Developer API): &lt;a href="https://firebase.google.com/docs/ai-logic" rel="noopener noreferrer"&gt;https://firebase.google.com/docs/ai-logic&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Firebase App Check: &lt;a href="https://firebase.google.com/docs/app-check" rel="noopener noreferrer"&gt;https://firebase.google.com/docs/app-check&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;flutter_ai_toolkit (Google, pub.dev): &lt;a href="https://pub.dev/packages/flutter_ai_toolkit" rel="noopener noreferrer"&gt;https://pub.dev/packages/flutter_ai_toolkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flutter 3.44 release notes: &lt;a href="https://docs.flutter.dev/release/release-notes" rel="noopener noreferrer"&gt;https://docs.flutter.dev/release/release-notes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Riverpod state management (pub.dev): &lt;a href="https://pub.dev/packages/riverpod" rel="noopener noreferrer"&gt;https://pub.dev/packages/riverpod&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Freezed package (pub.dev): &lt;a href="https://pub.dev/packages/freezed" rel="noopener noreferrer"&gt;https://pub.dev/packages/freezed&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;build_runner (pub.dev): &lt;a href="https://pub.dev/packages/build_runner" rel="noopener noreferrer"&gt;https://pub.dev/packages/build_runner&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Amiri Arabic font: &lt;a href="https://github.com/alif-type/amiri" rel="noopener noreferrer"&gt;https://github.com/alif-type/amiri&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IBM Plex Arabic font: &lt;a href="https://github.com/IBM/plex" rel="noopener noreferrer"&gt;https://github.com/IBM/plex&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;flutterfire CLI: &lt;a href="https://firebase.flutter.dev/docs/cli" rel="noopener noreferrer"&gt;https://firebase.flutter.dev/docs/cli&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Sayed Ali Alkamel is a Google Developer Expert in Dart and Flutter, co-founder of Flutter MENA, and Manager of Digital Application Platforms at Oman Housing Bank. He has spoken at tech events across 22+ countries and shipped apps with 2.5M+ downloads. He writes about Flutter, AI, and the developer experience at dev.to/sayed_ali_alkamel.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Spring AI: The Senior Dev's Honest Take on Java's AI Moment</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Wed, 17 Jun 2026 09:37:45 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/spring-ai-the-senior-devs-honest-take-on-javas-ai-moment-2g9c</link>
      <guid>https://dev.to/sayed_ali_alkamel/spring-ai-the-senior-devs-honest-take-on-javas-ai-moment-2g9c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spring AI&lt;/strong&gt; is an official Spring project that integrates AI models into Java using familiar patterns: auto-configuration, dependency injection, and vendor-portable abstractions.&lt;/li&gt;
&lt;li&gt;It reached &lt;strong&gt;1.0 GA in May 2025&lt;/strong&gt; and shipped &lt;strong&gt;1.1 in November 2025&lt;/strong&gt; with 850+ improvements, including full &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; integration.&lt;/li&gt;
&lt;li&gt;Supports &lt;strong&gt;20+ AI model providers&lt;/strong&gt; (OpenAI, Anthropic, Google, Ollama, and more) and &lt;strong&gt;12+ vector stores&lt;/strong&gt; behind a single consistent API.&lt;/li&gt;
&lt;li&gt;If your team runs Spring Boot, you have no good reason to write raw HTTP calls to an LLM anymore.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Problem Java Developers Did Not Expect to Have&lt;/li&gt;
&lt;li&gt;What Spring AI Actually Is&lt;/li&gt;
&lt;li&gt;The Core of the Framework&lt;/li&gt;
&lt;li&gt;A Fair Criticism Worth Naming&lt;/li&gt;
&lt;li&gt;Spring AI vs LangChain4j: The Honest Comparison&lt;/li&gt;
&lt;li&gt;What This Means For You&lt;/li&gt;
&lt;li&gt;Questions Developers Are Actually Asking About Spring AI&lt;/li&gt;
&lt;li&gt;Where This All Lands in Ten Years&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;About the Author&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The history of programming languages is mostly a history of incumbents surviving longer than anyone predicted. COBOL still processes an estimated $3 trillion in daily financial transactions. Fortran still appears in climate models and aerospace simulations. Java, introduced in 1995, became the substrate of the enterprise world and never left.&lt;/p&gt;

&lt;p&gt;Then AI models arrived and, for a brief window, seemed to break this pattern. The default language for working with language models was Python. Not because Python is architecturally superior. Because the researchers and tooling builders who created LangChain, HuggingFace integrations, and notebook-first workflows were Python developers who built for themselves first.&lt;/p&gt;

&lt;p&gt;The Java engineer maintaining the systems that actually run the bank, the logistics platform, or the hospital records management system found themselves looking at Python SDKs for a technology their organization was already being asked to adopt. The operating assumption forming in 2023 was that AI integration required a completely different stack. That assumption turned out to be wrong. &lt;strong&gt;Spring AI is the evidence.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Java Developers Did Not Expect to Have
&lt;/h2&gt;

&lt;p&gt;The uncomfortable reality of the 2023-2024 AI wave was that Java was not in the initial conversation.&lt;/p&gt;

&lt;p&gt;The fast-moving AI tooling ecosystem was almost entirely Python-native. The mental model that formed, correctly for a time, was that building AI applications required a stack switch, not just a skill addition. Java developers who had spent careers building production-grade distributed systems found themselves watching an entire category of tooling emerge around a language they did not use day-to-day.&lt;/p&gt;

&lt;p&gt;According to the 2024 BellSoft Java Survey, roughly 74% of Java developers already use AI tools in their day-to-day work, but only 34% were using any AI framework to build AI-powered applications. The gap between using AI and building with AI is exactly where Spring AI operates.&lt;/p&gt;

&lt;p&gt;The question most teams were facing was not philosophical. It was architectural: do we maintain existing Spring Boot microservices, or do we introduce a Python sidecar service to handle every LLM call? The two-process architecture feels manageable in a prototype and becomes a maintenance liability in production.&lt;/p&gt;

&lt;p&gt;Spring AI closes that gap. It brings AI model integration into the Spring container itself, using the same patterns your team already knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Spring AI Actually Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spring AI is an application framework that connects enterprise Java applications to AI models using portable abstractions, Spring Boot auto-configuration, and familiar dependency injection patterns.&lt;/strong&gt; It supports 20+ model providers and 12+ vector stores behind a single consistent API, with built-in RAG, tool calling, chat memory, and Model Context Protocol support. The project reached 1.0 GA in May 2025 and is production-ready today.&lt;/p&gt;

&lt;p&gt;The official description from the &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring team&lt;/a&gt; is precise: Spring AI applies core Spring design principles to the AI domain, specifically portability and modular design, and promotes using plain Java objects as the building blocks of AI applications.&lt;/p&gt;

&lt;p&gt;Think Spring Data, but for AI models instead of databases. The same philosophy applies. You describe what you want from a model without pinning your code to a specific vendor. If you have been writing &lt;code&gt;JpaRepository&lt;/code&gt; abstractions for years, the mental model transfers directly.&lt;/p&gt;

&lt;p&gt;Josh Long, Spring Developer Advocate at Broadcom, said it plainly at DevOps UK in 2025: building AI applications is mostly just calling models that have HTTP APIs. If you can build a Spring Boot service, you are already an AI developer. The skills transfer. The tooling just needed to catch up.&lt;/p&gt;

&lt;p&gt;Spring AI currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Over 20 AI model providers: OpenAI, Anthropic, Google Gemini, Amazon Bedrock, Azure OpenAI, Ollama, DeepSeek, Groq, Mistral AI, NVIDIA, Hugging Face, and more&lt;/li&gt;
&lt;li&gt;12+ vector stores: PostgreSQL/PGVector, Chroma, Pinecone, Weaviate, Milvus, Redis, MongoDB Atlas, Neo4j, Oracle, Qdrant, Apache Cassandra, and Azure Vector Search&lt;/li&gt;
&lt;li&gt;All major model types: chat completion, embeddings, text-to-image, audio transcription, text-to-speech, and moderation&lt;/li&gt;
&lt;li&gt;Agentic capabilities including tool calling, RAG pipelines, MCP client and server support, chat memory, and structured outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spring AI 2.0 is in active milestone development as of mid-2026, built on Spring Boot 4.0, Spring Framework 7.0, and a Jakarta EE 11 baseline, with Java 21 as the minimum runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core of the Framework
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The ChatClient: Your Main Entry Point
&lt;/h3&gt;

&lt;p&gt;The Spring container already knows your business logic. It has been holding your &lt;code&gt;@Service&lt;/code&gt; beans since the day you wrote them. Now it is making introductions, placing your existing code in the same room as whatever language model you are deploying that quarter.&lt;/p&gt;

&lt;p&gt;The primary API is &lt;code&gt;ChatClient&lt;/code&gt;, a fluent builder interface designed to feel like a sibling of &lt;code&gt;WebClient&lt;/code&gt; or &lt;code&gt;RestClient&lt;/code&gt;. Here is a working RAG-enabled chat endpoint with Spring AI 1.1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentChatController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ChatClient&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DocumentChatController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;ChatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;VectorStore&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chatClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;defaultAdvisors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QuestionAnswerAdvisor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ask"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four things are happening here that required significant boilerplate a year ago. The &lt;code&gt;QuestionAnswerAdvisor&lt;/code&gt; retrieves relevant documents from the vector store and injects them into the prompt automatically. The &lt;code&gt;VectorStore&lt;/code&gt; abstraction means you can swap PostgreSQL/PGVector for Pinecone without touching this controller. The model itself is configured in &lt;code&gt;application.yml&lt;/code&gt;. The entire thing is a standard &lt;code&gt;@RestController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is not a toy example. That is close to what a real document Q&amp;amp;A endpoint looks like in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching Providers Is a Configuration Decision, Not a Code Decision
&lt;/h3&gt;

&lt;p&gt;One of the more immediately useful things Spring AI does is treat provider selection as a deployment concern. To switch from OpenAI to Anthropic, you update &lt;code&gt;application.yml&lt;/code&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;# OpenAI setup&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;openai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${OPENAI_API_KEY}&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gpt-4o&lt;/span&gt;

&lt;span class="c1"&gt;# Anthropic setup (swap in, no code changes required)&lt;/span&gt;
&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;anthropic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ANTHROPIC_API_KEY}&lt;/span&gt;
      &lt;span class="na"&gt;chat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your controllers, services, and business logic stay unchanged. That portability is the central design promise of the framework, and in practice it holds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Function Calling and MCP: Where the Agent Story Gets Interesting
&lt;/h3&gt;

&lt;p&gt;Spring AI 1.1 introduced first-class Model Context Protocol (MCP) integration, and this is the most strategically significant part of the framework for teams building agentic systems. MCP is the emerging standard for interoperability between AI agents and the external tools they need to call.&lt;/p&gt;

&lt;p&gt;With Spring AI, you register existing business logic as AI-callable functions with minimal code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankingTools&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@Description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetch the account balance for a given customer ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BalanceRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AccountBalance&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;accountBalance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;AccountRepository&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;AccountBalance:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The framework picks up this bean automatically and makes it available to the model via function calling. The AI can now invoke your business logic directly, with full type safety and no change to the underlying service implementation.&lt;/p&gt;

&lt;p&gt;Jonathan Schneider made a comparison in 2025 that the Spring community has been repeating since: function calling is to RAG what Inversion of Control was to Java development. IoC did not just clean up dependency management. It changed how teams thought about component design entirely. Function calling through MCP does something similar for the relationship between your business logic and AI orchestration.&lt;/p&gt;

&lt;h3&gt;
  
  
  What 850 Improvements Actually Means at Human Scale
&lt;/h3&gt;

&lt;p&gt;Spring AI 1.1 shipped with 850+ improvements across five milestone builds. If each improvement were one second of continuous work, that is over 14 minutes of fixes, enhancements, and documentation updates streaming out in a single release cycle. In concrete terms: 354 enhancements, 241 bug fixes, and 100 documentation improvements. This is a framework in rapid production maturation, not a research prototype catching up to itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Fair Criticism Worth Naming
&lt;/h2&gt;

&lt;p&gt;The fair criticism here is direct: Spring AI is only the right tool if you are already in the Spring ecosystem. The portability abstractions are genuinely useful, but they sit on top of Spring Boot auto-configuration. If your team runs Quarkus, Micronaut, or bare Vert.x, the first-class integration story does not apply to you.&lt;/p&gt;

&lt;p&gt;LangChain4j, the main Java alternative, has native Quarkus extensions maintained by Red Hat, supports a broader raw count of LLM providers out of the box (30+ versus Spring AI's 20+), and does not require Spring Boot as a foundation.&lt;/p&gt;

&lt;p&gt;I want to name one real limitation I ran into during production evaluation: the &lt;a href="https://docs.spring.io/spring-ai/reference/api/advisors.html" rel="noopener noreferrer"&gt;Advisors API documentation&lt;/a&gt; is genuinely thin in places. I ended up reading Spring AI integration tests more than the reference guide to understand how chained advisors compose when you combine RAG retrieval with conversation memory. The team is improving this, but if you are onboarding a new engineer onto a Spring AI RAG pipeline today, budget extra time for that gap.&lt;/p&gt;

&lt;p&gt;On the question of performance: LLM network latency dwarfs any Java framework overhead by two to three orders of magnitude. A detailed Java ecosystem analysis from early 2026 confirmed this directly: the model round trip is always the bottleneck, not the abstraction layer. Spring AI adds no meaningful latency to an operation that already takes hundreds of milliseconds or more.&lt;/p&gt;

&lt;p&gt;One more honest thing to name: the API surface is still moving. Teams that pinned to Spring AI 1.0 found some adapter interfaces changed in 1.1. The upgrade path from 1.x to 2.0 will require careful attention given the Jakarta EE 11 baseline and Spring Boot 4 foundation shift. This is not a reason to avoid Spring AI. It is a reason to track minor versions more actively than you would Spring Data or Spring Security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring AI vs LangChain4j: The Honest Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Spring AI 1.1&lt;/th&gt;
&lt;th&gt;LangChain4j 1.x&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary target&lt;/td&gt;
&lt;td&gt;Spring Boot teams&lt;/td&gt;
&lt;td&gt;Any JVM stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI provider count&lt;/td&gt;
&lt;td&gt;20+&lt;/td&gt;
&lt;td&gt;30+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector store count&lt;/td&gt;
&lt;td&gt;12+&lt;/td&gt;
&lt;td&gt;30+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP support&lt;/td&gt;
&lt;td&gt;First-class&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG tooling&lt;/td&gt;
&lt;td&gt;Advisors API, ETL pipeline&lt;/td&gt;
&lt;td&gt;Mature, granular pipeline control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quarkus / Micronaut support&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Native extensions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Spring Boot Actuator native&lt;/td&gt;
&lt;td&gt;Requires separate setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool calling style&lt;/td&gt;
&lt;td&gt;Spring beans as functions&lt;/td&gt;
&lt;td&gt;Explicit &lt;code&gt;@Tool&lt;/code&gt; annotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA release&lt;/td&gt;
&lt;td&gt;May 2025&lt;/td&gt;
&lt;td&gt;May 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The decision rule that emerges here is clean. Spring Boot team with existing observability and Spring Security setup? Spring AI is the path of least friction and lowest long-term maintenance cost. Non-Spring stack, or you need the broader provider catalog? LangChain4j is the reasonable alternative. Both are production-ready. The cost of switching six months into a project is real, so treat this as an architectural decision, not an implementation detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means For You
&lt;/h2&gt;

&lt;p&gt;If you are a backend engineer on a Spring Boot team, the question is not whether to use Spring AI. It is which capability to start with.&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;ChatClient&lt;/code&gt; and a single provider. Get one endpoint working end to end. Add a &lt;code&gt;VectorStore&lt;/code&gt; when you have a genuine document retrieval use case. Do not attempt RAG, MCP, tool calling, and observability simultaneously on the first sprint. The abstractions are composable by design, and you can add each layer when you actually need it.&lt;/p&gt;

&lt;p&gt;If you are building internal tooling or copilot-style features over your platform's existing knowledge base (the category I am actively evaluating at OHB for our Digital Application Platforms team), the Advisors API and chat memory support are the most immediately useful pieces. They let you build context-aware assistants without standing up a Python sidecar service and all the operational overhead that brings.&lt;/p&gt;

&lt;p&gt;For teams deciding whether to standardize on Spring AI for the long term: Spring AI 2.0 on Spring Boot 4 with Java 21 virtual threads is a serious platform for production agentic applications. The team shipped 2.0.0-M4 in March 2026 and RC1 in June 2026. General availability is close, and the migration story from 1.x is well-documented.&lt;/p&gt;

&lt;p&gt;The Spring AI Community GitHub organization, announced at Spring I/O Barcelona 2025, has also created a formal home for community integrations and experimental projects that the core team cannot absorb directly. It is a signal of a framework that is building a serious ecosystem around itself, not just a well-funded core team working in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions Developers Are Actually Asking About Spring AI
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Spring AI and how is it different from calling an LLM API directly?
&lt;/h3&gt;

&lt;p&gt;Spring AI is an official Spring Framework project that provides portable abstractions for integrating AI models into Java applications, following the same design philosophy as Spring Data or Spring Cache. It abstracts vendor-specific HTTP APIs behind consistent interfaces so your application code does not change when you switch providers. Calling an LLM API directly works fine for a simple one-off integration but creates vendor lock-in, lacks built-in support for RAG, tool calling, and chat memory, and produces code that does not compose with your existing Spring observability and testing infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  When did Spring AI reach production readiness?
&lt;/h3&gt;

&lt;p&gt;Spring AI 1.0 reached General Availability on May 20, 2025, announced by Mark Pollack, Christian Tzolov, and Josh Long. Spring AI 1.1 followed in November 2025 with 850+ improvements including full Model Context Protocol support and a structured Advisors API. As of June 2026, version 2.0 RC1 is available, built on Spring Boot 4.0, Spring Framework 7.0, and a Jakarta EE 11 baseline with Java 21 as the minimum runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Spring AI support running models locally with Ollama?
&lt;/h3&gt;

&lt;p&gt;Yes. Ollama is a first-class provider in Spring AI, configured in &lt;code&gt;application.yml&lt;/code&gt; exactly like any cloud provider. This means you can run a Spring AI application against a locally hosted Llama, Gemma, or Mistral model during development and switch to a cloud provider in production without changing any application code. The abstraction layer makes provider selection a configuration decision, not a refactor.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Advisors API in Spring AI and why does it matter?
&lt;/h3&gt;

&lt;p&gt;The Advisors API is Spring AI's abstraction for encapsulating recurring AI patterns, like retrieving context from a vector store before sending a prompt (Retrieval Augmented Generation), or maintaining conversation history across requests (chat memory). Instead of wiring this logic manually in every controller, you register advisors once and they apply transparently to every &lt;code&gt;ChatClient&lt;/code&gt; interaction. It is the AI equivalent of Spring AOP: cross-cutting concerns extracted from your business logic and applied declaratively.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Spring AI handle MCP support?
&lt;/h3&gt;

&lt;p&gt;Spring AI 1.1 introduced first-class MCP support, allowing you to both consume MCP-compliant tool servers and expose your own Spring beans as MCP servers. Existing Spring-managed functions annotated with &lt;code&gt;@Description&lt;/code&gt; become callable by any MCP-compatible AI agent, with the framework handling protocol compliance, tool registration, and multi-protocol version negotiation. OAuth2-secured MCP connections were included in the 1.1 development cycle, making production-grade deployment realistic from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use Spring AI or LangChain4j for a new project?
&lt;/h3&gt;

&lt;p&gt;If your team is already on Spring Boot, Spring AI integrates natively with your existing auto-configuration, Actuator observability, and Spring Security setup, and tool calling works through your existing Spring beans with no separate registration step. If you are running Quarkus, Micronaut, or you need a broader catalog of out-of-the-box LLM provider integrations, LangChain4j is the more practical choice. Both frameworks hit 1.0 GA in May 2025 and are production-ready today. The team's existing stack is the primary deciding factor, not the frameworks themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This All Lands in Ten Years
&lt;/h2&gt;

&lt;p&gt;Java has been declared dead so many times that the obituaries have obituaries. In 1996, it was going to be displaced by browser applets doing the wrong thing. In 2010, by dynamic scripting languages. In 2015, by Go and Rust eating the systems programming space. In 2023, it was apparently going to be bypassed entirely by Python notebook culture as the substrate for AI development.&lt;/p&gt;

&lt;p&gt;What keeps not happening is the death of the enterprise JVM. Too many critical systems. Too much accumulated organizational knowledge. Too many engineers who think clearly in types and interfaces and who understand what it costs to rewrite something that is actually working.&lt;/p&gt;

&lt;p&gt;Spring AI is not a Java comeback story. Java never left. What Spring AI represents is the formal acknowledgment by the Spring team that the patterns which made Java productive in distributed systems, abstraction behind interfaces, inversion of control, configuration as a deployment concern rather than a code concern, are the same patterns that make AI integration maintainable at enterprise scale. The same principles that made Spring Data the default for database access in Java are now making Spring AI the default for model access.&lt;/p&gt;

&lt;p&gt;The developer role is not disappearing. It is going somewhere more interesting. The systems that AI agents will build in ten years will themselves need to be designed, tested, debugged, and operated by engineers who understand how abstractions compose under production load. The engineers who learned those skills building Spring Boot microservices are, it turns out, unusually well-prepared for what is coming.&lt;/p&gt;

&lt;p&gt;We just needed the framework to catch up.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Spring AI Official Project Overview: &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;https://spring.io/projects/spring-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI Core Concepts Documentation: &lt;a href="https://docs.spring.io/spring-ai/reference/concepts.html" rel="noopener noreferrer"&gt;https://docs.spring.io/spring-ai/reference/concepts.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI 1.0 GA Release Announcement (May 20, 2025): &lt;a href="https://spring.io/blog/2025/05/20/your-first-spring-ai-1/" rel="noopener noreferrer"&gt;https://spring.io/blog/2025/05/20/your-first-spring-ai-1/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI 1.1 GA Release Notes (November 12, 2025): &lt;a href="https://spring.io/blog/2025/11/12/spring-ai-1-1-GA-released/" rel="noopener noreferrer"&gt;https://spring.io/blog/2025/11/12/spring-ai-1-1-GA-released/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI 2.0.0-M1 Release Announcement (December 11, 2025): &lt;a href="https://spring.io/blog/2025/12/11/spring-ai-2-0-0-M1-available-now/" rel="noopener noreferrer"&gt;https://spring.io/blog/2025/12/11/spring-ai-2-0-0-M1-available-now/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI 2.0.0-M4, 1.1.4, and 1.0.5 Release (March 2026): &lt;a href="https://spring.io/blog/2026/03/26/spring-ai-2-0-0-M4-and-1-1-4-and-1-0-5-available/" rel="noopener noreferrer"&gt;https://spring.io/blog/2026/03/26/spring-ai-2-0-0-M4-and-1-1-4-and-1-0-5-available/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI Community GitHub Organization Announcement: &lt;a href="https://spring.io/blog/2025/10/07/spring-ai-community-announcement/" rel="noopener noreferrer"&gt;https://spring.io/blog/2025/10/07/spring-ai-community-announcement/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI Advisors API Reference Documentation: &lt;a href="https://docs.spring.io/spring-ai/reference/api/advisors.html" rel="noopener noreferrer"&gt;https://docs.spring.io/spring-ai/reference/api/advisors.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Choosing a Java LLM Strategy in 2026: Spring AI vs LangChain4j, Java Code Geeks: &lt;a href="https://www.javacodegeeks.com/2026/03/choosing-a-java-llm-integration-strategy-in-2026-spring-ai-1-1-vs-langchain4j-vs-direct-api-calls.html" rel="noopener noreferrer"&gt;https://www.javacodegeeks.com/2026/03/choosing-a-java-llm-integration-strategy-in-2026-spring-ai-1-1-vs-langchain4j-vs-direct-api-calls.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The State of Coding the Future with Java and AI, Microsoft Java Developer Blog (May 2025): &lt;a href="https://devblogs.microsoft.com/java/the-state-of-coding-the-future-with-java-and-ai/" rel="noopener noreferrer"&gt;https://devblogs.microsoft.com/java/the-state-of-coding-the-future-with-java-and-ai/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Josh Long on Spring AI, AI Native Dev Podcast at DevOps UK (May 2025): &lt;a href="https://tessl.io/podcast/josh-long/" rel="noopener noreferrer"&gt;https://tessl.io/podcast/josh-long/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring AI Integration: Building Intelligent Java Applications, Java Code Geeks (November 2025): &lt;a href="https://www.javacodegeeks.com/2025/11/spring-ai-integration-building-intelligent-java-applications.html" rel="noopener noreferrer"&gt;https://www.javacodegeeks.com/2025/11/spring-ai-integration-building-intelligent-java-applications.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spring Boot 4, Spring AI, and AI-First Java Development, Java Code Geeks (March 2026): &lt;a href="https://www.javacodegeeks.com/2026/03/spring-boot-4-spring-ai-and-ai-first-java-development.html" rel="noopener noreferrer"&gt;https://www.javacodegeeks.com/2026/03/spring-boot-4-spring-ai-and-ai-first-java-development.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LangChain4j vs Spring AI: An Honest Side-by-Side for Java Backend Teams, Level Up Coding: &lt;a href="https://levelup.gitconnected.com/langchain4j-vs-spring-ai-an-honest-side-by-side-for-java-backend-teams-b6c0ea370f28" rel="noopener noreferrer"&gt;https://levelup.gitconnected.com/langchain4j-vs-spring-ai-an-honest-side-by-side-for-java-backend-teams-b6c0ea370f28&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Sayed Ali Alkamel is a Google Developer Expert in Dart and Flutter, co-founder of Flutter MENA, and Manager of Digital Application Platforms at Oman Housing Bank. He has spoken at tech events across 22+ countries and shipped apps with 2.5M+ downloads. He writes about Flutter, AI, and the developer experience at dev.to/sayed_ali_alkamel.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>The New SDLC: A Senior Dev's Honest Take on Vibe Coding and Agentic Engineering</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Tue, 16 Jun 2026 14:52:29 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/the-new-sdlc-a-senior-devs-honest-take-on-vibe-coding-and-agentic-engineering-55m7</link>
      <guid>https://dev.to/sayed_ali_alkamel/the-new-sdlc-a-senior-devs-honest-take-on-vibe-coding-and-agentic-engineering-55m7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vibe coding&lt;/strong&gt; is not a development methodology. It is an attitude toward verification: optional. That attitude does not scale past prototypes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic engineering&lt;/strong&gt; puts AI inside a scaffold of constraints, tests, and feedback loops. The developer's primary output becomes the system that produces code, not the code itself.&lt;/li&gt;
&lt;li&gt;The key formula from the Google whitepaper: &lt;strong&gt;Agent = Model + Scaffold&lt;/strong&gt;. Most teams invest heavily in the model and ignore the scaffold. That is backwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context engineering&lt;/strong&gt; separates fast AI output from useful AI output. What you put in the context window matters more than which model you pick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The factory model&lt;/strong&gt; means your job is now designing the assembly line, not assembling each widget by hand.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What the New SDLC Actually Looks Like in 2026&lt;/li&gt;
&lt;li&gt;The Agent Scaffold: What Actually Surrounds the Model&lt;/li&gt;
&lt;li&gt;Context Engineering Is Not Prompt Engineering&lt;/li&gt;
&lt;li&gt;AI Makes Experienced Developers Slower? About That&lt;/li&gt;
&lt;li&gt;The Factory Model: Building the System That Builds Software&lt;/li&gt;
&lt;li&gt;What This Means For You&lt;/li&gt;
&lt;li&gt;Questions Developers Are Actually Asking About Agentic Engineering&lt;/li&gt;
&lt;li&gt;The Deeper Arc&lt;/li&gt;
&lt;li&gt;References&lt;/li&gt;
&lt;li&gt;About the Author&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Andrej Karpathy coined "vibe coding" in February 2025, and by early 2026 he was already calling it passé. The replacement he introduced at Sequoia Capital's AI Ascent 2026 event was "agentic engineering." Not because the tools changed names, but because the practice had matured past the point where the original framing was useful. By December 2025, he reported, AI agents were writing approximately 80% of his code. The question was no longer whether to let AI write code. The question was how to govern what comes back.&lt;/p&gt;

&lt;p&gt;To understand why the framing change matters, you have to understand what programming has always actually been.&lt;/p&gt;

&lt;p&gt;For 70 years, the deal between developers and machines was a one-sided negotiation. You learned the machine's language. Binary, then assembly, then C, then Java, then whatever framework won last year's framework wars. Every generation of languages reduced the translation tax, but never eliminated the fundamental transaction: human intent, compressed into syntax the machine could execute. The reason curly braces exist is not because they reflect how humans think. They exist because the parser needs them. Syntax was always just the price of admission. What large language models changed is that, for the first time, the machine is reaching back toward intent. The syntax tax is collapsing. What replaced it is a verification tax. And that tax is what the new SDLC is built around paying correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the New SDLC Actually Looks Like in 2026
&lt;/h2&gt;

&lt;p&gt;As of early 2026, JetBrains data shows that 85% of professional developers regularly use AI coding tools, and 51% use them daily. Around 41% of all new code is estimated to be AI-generated. To make that concrete: in a standard ten-file Flutter feature, roughly four of those files were not typed by a human hand. The developer who reviews more code than they write is no longer exceptional. They are average. &lt;/p&gt;

&lt;p&gt;The Google whitepaper "The New SDLC with Vibe Coding" (Osmani, Saboo, Kartakis, May 2026) frames the landscape as a spectrum, not a binary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Vibe Coding&lt;/th&gt;
&lt;th&gt;Structured AI-Assisted&lt;/th&gt;
&lt;th&gt;Agentic Engineering&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Intent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Casual natural language prompts&lt;/td&gt;
&lt;td&gt;Detailed prompts with examples&lt;/td&gt;
&lt;td&gt;Formal specs, architecture docs, rule files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Does it seem to work?"&lt;/td&gt;
&lt;td&gt;Manual testing, spot-checks&lt;/td&gt;
&lt;td&gt;Automated test suites, CI/CD gates, LM judges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Paste error back into the prompt&lt;/td&gt;
&lt;td&gt;Developer diagnoses root cause&lt;/td&gt;
&lt;td&gt;Agents self-diagnose within defined bounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Codebase understanding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developer may not read the generated code&lt;/td&gt;
&lt;td&gt;Selective review of critical paths&lt;/td&gt;
&lt;td&gt;Comprehensive architecture review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prototypes, hackathons, personal scripts&lt;/td&gt;
&lt;td&gt;Features in established codebases&lt;/td&gt;
&lt;td&gt;Production systems, team-scale delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (acceptable for throwaway code)&lt;/td&gt;
&lt;td&gt;Moderate (human judgment at key checkpoints)&lt;/td&gt;
&lt;td&gt;Low (systematic verification at every stage)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key insight from the table: the differentiator is not which model you use. It is how much structure, verification, and human judgment surrounds the model's output.&lt;/p&gt;

&lt;p&gt;I've been watching this pattern up close at Oman Housing Bank. The moment our team shifted from casual prompting to writing explicit rule files first, the quality of AI output changed more than any model upgrade had produced. The model had not changed. The context around the model had.&lt;/p&gt;

&lt;p&gt;The traditional SDLC moved from waterfall to Agile over about two decades. AI is compressing the current iteration cycle dramatically, but unevenly. Implementation that once took a week can now take hours. Requirements, architecture, and verification remain stubbornly human-paced. The result is a different workflow, not a faster version of the old one. Boundaries between phases blur. Iteration cycles shorten from weeks to minutes in some areas. The developer's role shifts from primary implementor to system designer and quality arbiter.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Agent Scaffold: What Actually Surrounds the Model
&lt;/h2&gt;

&lt;p&gt;There is a temptation, when adopting AI coding tools, to treat the model as the system. A newer model comes out and the agent gets smarter. An older one and it gets worse. The model becomes the explanation for everything good and bad.&lt;/p&gt;

&lt;p&gt;That intuition leads to the wrong investments.&lt;/p&gt;

&lt;p&gt;The model is one input into a running agent. Everything else, the prompts, the tools, the rule files, the test runners, the observability layer, is the scaffold: the machinery that gives the model state, feedback, and constraints. The whitepaper equation is clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent = Model + Scaffold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model is not psychic. It arrives at your repository with the same blank-slate uncertainty as a new engineer on their first day, except it cannot ask questions. It will infer. Without a rule file, those inferences will be wrong for your specific project in predictable ways: wrong state management pattern, wrong folder structure, wrong testing convention, wrong import path. The scaffold is the onboarding document the new engineer never gets to ask you to write.&lt;/p&gt;

&lt;p&gt;Public benchmarks make the impact concrete. On Terminal Bench 2.0, one team moved a coding agent from outside the top 30 to the top 5 by changing only the scaffold, with no model change. A LangChain study raised an agent's benchmark score by 13.7 points by tweaking only the system prompt and middleware around a fixed model. Most agent failures, examined honestly, are scaffold configuration failures.&lt;/p&gt;

&lt;p&gt;A practical scaffold for a Flutter project looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# AGENTS.md&lt;/span&gt;

&lt;span class="gu"&gt;## Stack&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Flutter 3.32+, Dart 3.8+
&lt;span class="p"&gt;-&lt;/span&gt; Riverpod for state management
&lt;span class="p"&gt;-&lt;/span&gt; Freezed for data classes
&lt;span class="p"&gt;-&lt;/span&gt; dio + retrofit for networking

&lt;span class="gu"&gt;## Architecture&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Hexagonal architecture. Feature folders, not layer folders.
&lt;span class="p"&gt;-&lt;/span&gt; Domain layer has zero Flutter imports.
&lt;span class="p"&gt;-&lt;/span&gt; Repositories are interfaces. Concrete implementations live in the data layer.
&lt;span class="p"&gt;-&lt;/span&gt; UI components are stateless where possible. State lives in providers.

&lt;span class="gu"&gt;## Hard Rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Never add a package not already in pubspec.yaml. Ask first.
&lt;span class="p"&gt;-&lt;/span&gt; Tests live in test/, not alongside source files.
&lt;span class="p"&gt;-&lt;/span&gt; No print() statements. Use the Logger package.
&lt;span class="p"&gt;-&lt;/span&gt; Named parameters for any function with more than 2 arguments.
&lt;span class="p"&gt;-&lt;/span&gt; Run flutter analyze before considering a task complete.
&lt;span class="p"&gt;-&lt;/span&gt; Null safety is non-negotiable. No dynamic, no implicit nulls.

&lt;span class="gu"&gt;## Workflow&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Read the relevant feature spec before generating any code.
&lt;span class="p"&gt;2.&lt;/span&gt; Write the test first. Then implement to make the test pass.
&lt;span class="p"&gt;3.&lt;/span&gt; If the change touches the domain layer, flag it for human review before proceeding.
&lt;span class="p"&gt;4.&lt;/span&gt; After implementation, confirm that flutter test and flutter analyze both pass.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is not prompting. It is operating instructions. The developer who wrote it spent about an hour. Every subsequent AI interaction inside that project benefits from that hour. It compounds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Context Engineering Is Not Prompt Engineering
&lt;/h2&gt;

&lt;p&gt;A key distinction from the whitepaper: prompt engineering and context engineering are not the same practice.&lt;/p&gt;

&lt;p&gt;Prompt engineering asks "how do I phrase this question to get a smarter answer from the model?" Context engineering asks "what would a new team member need to know to contribute correctly to this project, and how do I encode that knowledge in a form the AI can use?"&lt;/p&gt;

&lt;p&gt;The difference in daily practice is significant. Prompt engineering optimizes a single turn. Context engineering optimizes the entire session, and when the context persists across sessions, it optimizes every future session as well.&lt;/p&gt;

&lt;p&gt;Six types of context matter in any agentic workflow. &lt;strong&gt;Instructions&lt;/strong&gt; define the agent's role, constraints, and operating bounds. &lt;strong&gt;Knowledge&lt;/strong&gt; includes retrieved documents, architectural diagrams, and domain-specific data. &lt;strong&gt;Memory&lt;/strong&gt; covers short-term session state (what just happened) and long-term project state (what the project is). &lt;strong&gt;Examples&lt;/strong&gt; provide few-shot behavioral patterns from your own codebase, not generic patterns from the internet. &lt;strong&gt;Tools&lt;/strong&gt; are the precise definitions of APIs, scripts, and services the agent can call. &lt;strong&gt;Guardrails&lt;/strong&gt; are the hard constraints it cannot cross.&lt;/p&gt;

&lt;p&gt;The docs around context engineering are genuinely thin right now. Most tools tell you to "add rules" without being specific about what granularity actually works in practice. I lost more time than I'd like to admit figuring out the line between rule files that were too broad to help and rule files too verbose to fit efficiently in context. The working heuristic: a rule should be short, specific, and paired with a concrete example of the behavior you want.&lt;/p&gt;

&lt;p&gt;Static context (rule files, system instructions) is always loaded and expensive: every token is paid on every model call, regardless of relevance. Dynamic context (tool results, retrieved documents, windowed session history) is loaded on demand and costs only when the information is actually needed. Deciding what belongs in static versus dynamic context is a real engineering trade-off, worth the same deliberate attention as any other architectural decision.&lt;/p&gt;

&lt;p&gt;The most effective pattern for managing dynamic context is what the whitepaper calls Agent Skills: structured, portable packages of procedural knowledge the agent loads only when the task matches. The agent sees lightweight metadata at startup, loads full instructions when a task matches, and pulls deep reference material only when explicitly needed. This is precisely what I've been exploring in the Flutter Agent Skills series.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Makes Experienced Developers Slower? About That
&lt;/h2&gt;

&lt;p&gt;The fair criticism of AI coding tools is not that they produce bad code. The fair criticism is that experienced developers using early AI tools were, in controlled conditions, measurably slower on complex tasks.&lt;/p&gt;

&lt;p&gt;METR published exactly this finding in July 2025. Sixteen experienced open-source contributors, working on repositories averaging 22,000 stars and over a million lines of code, took 19% longer on tasks when using AI tools. The same developers estimated they had been 20% faster. The gap between perception and measured reality was not a minor miscalibration. It was a complete disconnect, as MIT Technology Review noted in its coverage of the study.&lt;/p&gt;

&lt;p&gt;METR's February 2026 follow-up complicated the picture. Their original methodology had a sampling problem: the developers who benefited most from AI tools refused to participate in no-AI conditions, even at $50/hour incentives. Heavy AI adopters systematically excluded themselves from the control group. The revised conclusion was genuinely honest: "We do not know if AI makes developers more productive."&lt;/p&gt;

&lt;p&gt;What the broader data does show clearly is this: developers who use AI for well-specified, implementation-heavy tasks and maintain strong review practices report gains of 25 to 39% (JetBrains, Index.dev, 2026). Developers who accept output without review see bugs increase by 41% in some studies, and code quality deteriorate through increased churn and duplication.&lt;/p&gt;

&lt;p&gt;The productivity gain from agentic engineering is real. But it is conditional. The condition is structure. Unstructured AI assistance in 2025 was slower because it introduced verification overhead without reducing specification overhead. Agentic engineering addresses this by investing upfront in the spec, the scaffold, and the test suite, so that the AI's implementation overhead replaces genuinely slower human implementation, rather than adding on top of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Factory Model: Building the System That Builds Software
&lt;/h2&gt;

&lt;p&gt;The mental model that ties everything above together is what the whitepaper calls the factory model. In this model, the developer's primary output is not code. It is the system that produces code.&lt;/p&gt;

&lt;p&gt;That system includes specifications and context that define what needs to be built, agents that translate specifications into implementation, tests and quality gates that verify correctness, feedback loops that route failures back to agents for correction, and guardrails that constrain agents to predictable, safe behavior.&lt;/p&gt;

&lt;p&gt;A factory manager does not assemble every widget by hand. They design the assembly line and ensure quality control. The modern developer designs the development system and ensures its output meets the required standard.&lt;/p&gt;

&lt;p&gt;This reframes the developer's role in terms of two operating modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conductor mode&lt;/strong&gt;: real-time, hands-on direction. The developer is in the IDE, watching code appear, guiding with prompts and corrections, maintaining fine-grained control over what gets written. Tools like GitHub Copilot, Gemini Code Assist, Cursor, and Windsurf primarily support this mode. It is natural for engineers from traditional backgrounds because it preserves a sense of understanding at each step. The risk: if the developer personally directs every interaction, the throughput improvement from AI stays limited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orchestrator mode&lt;/strong&gt;: the developer operates at a higher abstraction level. They define goals, assign them to agents, and review results without watching code appear line by line. Claude Code, GitHub Copilot's agent mode, Google Jules, and similar tools support this through async task execution in sandboxed environments. This mode is best for well-defined tasks: bug fixes, feature implementations against established patterns, test generation, codebase migrations.&lt;/p&gt;

&lt;p&gt;The orchestrator mode requires a different skill set: writing precise specifications so an agent can execute without ambiguity, decomposing large tasks into appropriately sized units, quickly evaluating whether agent output meets the quality standard, and designing the constraints and feedback loops that keep agents productive.&lt;/p&gt;

&lt;p&gt;Most developers move between both modes in a single day. The honest limitation here: orchestrator mode requires more upfront discipline, not less. You have to write the spec before you can delegate the implementation. Teams under sprint pressure will find this counterintuitive. The payoff comes in the second sprint, not the first. Teams that treat AI tooling as a shortcut to avoid specification work end up producing prototypes that ship by accident.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means For You
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For individual developers&lt;/strong&gt;, the first concrete action is writing a rule file for each project you actively use AI on. Start with ten lines: stack, architecture constraints, hard rules, testing conventions. Add a rule every time an agent does something it should not do again. Over a few weeks, you will have a project-specific configuration that makes AI output dramatically more useful without any change to the model.&lt;/p&gt;

&lt;p&gt;Write tests and evaluations before generating code. A well-written test suite communicates intent to the agent more precisely than any natural-language prompt. It also gives the agent something to run, fail against, and correct toward. A test is a specification that a machine can verify.&lt;/p&gt;

&lt;p&gt;Review every line the agent produces that is going to ship. Be skeptical of anything that looks clever. Check that imported packages actually exist. Verify that error handling covers realistic failure modes, not just the happy path. Code your team does not understand becomes debugging cost your team cannot afford. This is doubly true in regulated industries like banking and fintech, where I have watched well-generated code fail in ways the agent never anticipated because it had no context for the regulatory constraint that made the edge case matter.&lt;/p&gt;

&lt;p&gt;Maintain your core engineering skills deliberately. AI handles the routine so you can focus on the difficult. That arrangement only works if the foundational skills, debugging, system design, intuition for performance and correctness, stay sharp. Treat AI as a way to apply expertise at greater scale, not as a substitute for developing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For engineering leaders&lt;/strong&gt;, treat rule files, system prompts, eval suites, and skill libraries as code: versioned, reviewed in pull requests, owned by named engineers. Without this discipline, agent behavior becomes irreproducible across team members.&lt;/p&gt;

&lt;p&gt;Set the bar at the evaluation, not the demo. A working demo proves an agent can succeed once. A passing eval suite proves it succeeds reliably. A demo is a sample size of one.&lt;/p&gt;

&lt;p&gt;Distinguish prototyping work from production work in team norms. Vibe coding is the right speed for exploration. Agentic engineering is the right discipline for production. Make the boundary explicit: which projects, which branches, which environments warrant which mode of working. Teams that keep this distinction blurry produce prototypes that ship by accident.&lt;/p&gt;




&lt;h2&gt;
  
  
  Questions Developers Are Actually Asking About Agentic Engineering
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between vibe coding and agentic engineering?
&lt;/h3&gt;

&lt;p&gt;Vibe coding means prompting an AI, accepting the output, and using "does it seem to work?" as the sole verification criterion. Agentic engineering means AI operates as an implementation engine inside a structure of formal specs, rule files, automated tests, and feedback loops, with humans retaining oversight over architecture, correctness, and quality. The differentiator is not which tools you use. It is how much structure surrounds the model's output. A developer can vibe code or apply agentic engineering using the exact same agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is context engineering in software development?
&lt;/h3&gt;

&lt;p&gt;Context engineering is the discipline of structuring and curating the information provided to an AI agent so it can act correctly on your specific codebase, architecture, and constraints. It goes beyond prompt phrasing to include rule files (AGENTS.md, CLAUDE.md), retrieved documentation, few-shot examples from your own code patterns, tool definitions, and hard guardrails. Research and practitioner evidence consistently shows that the quality of AI-generated code depends less on how clever the prompt is and more on how well the context reflects what your project actually requires.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should go in an AGENTS.md or CLAUDE.md file?
&lt;/h3&gt;

&lt;p&gt;A rule file should include your stack and versions, architectural patterns and conventions (the patterns you actually use, not generic ones), hard rules the agent cannot break such as packages to never add or patterns to avoid, your testing approach and where tests live, and a workflow the agent should follow before generating code. Start with ten lines and add rules when the agent does something wrong. A short, high-signal rule file consistently outperforms a long, sprawling one because every token in static context is paid on every model call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is AI actually making developers more productive in 2026?
&lt;/h3&gt;

&lt;p&gt;The evidence is mixed and strongly context-dependent. JetBrains and Index.dev data puts self-reported productivity gains at 25 to 39% for developers using AI tools regularly. METR's July 2025 study found experienced developers took 19% longer on complex tasks with AI tools, though their February 2026 follow-up acknowledged that heavy AI adopters had systematically excluded themselves from the control group, undermining the result. The clearest consistent finding: developers who use AI for well-specified implementation tasks and maintain strong review practices gain substantially more than those who accept output without verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the 80% problem in AI-assisted development?
&lt;/h3&gt;

&lt;p&gt;The 80% problem refers to the observation that AI agents can rapidly generate approximately 80% of the code for any feature, but the remaining 20% (edge cases, error handling, integration points, and subtle correctness requirements) demands deep contextual knowledge that current models consistently lack. This remaining 20% is where most production failures originate. It requires human architectural judgment and domain expertise that cannot be delegated to an agent without very precisely specified context. The developers who navigate this most effectively use AI for the well-specified 80% while reserving their own attention for the 20% that requires knowing why the constraint exists, not just that it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the factory model change the developer's role?
&lt;/h3&gt;

&lt;p&gt;The factory model treats the developer's primary output as the system that produces code, not the code itself. The system includes specifications, agent configurations, test suites, feedback loops, and guardrails. A factory manager does not assemble every widget by hand. They design the assembly line and ensure quality control. The valuable skill shifts from syntax fluency to specification quality, architectural judgment, and evaluation rigor. Karpathy's framing at AI Ascent 2026 captures it: "You are not writing the code directly 99% of the time. You are orchestrating agents who do, and acting as oversight."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deeper Arc
&lt;/h2&gt;

&lt;p&gt;Step back far enough and there is a pattern here that predates every modern framework.&lt;/p&gt;

&lt;p&gt;In the 1950s, the bottleneck was hardware availability: only a handful of institutions had machines worth programming. In the 1970s, it was languages: only specialists who could reason in assembly were useful. In the 1990s, it was networking and distribution. In the 2010s, it was infrastructure: who had enough servers. In each transition, the skills that mattered most shifted, and a new population of people gained the ability to build things that had previously required a much smaller, more specialized group.&lt;/p&gt;

&lt;p&gt;Every abstraction layer in computing history made the skills below it less central. Writing directly in assembly became less valuable when C arrived. Knowing the details of TCP/IP became less essential when frameworks abstracted the network. And critically, in every case, the engineers who treated the new abstraction as a threat to their skills were not wrong that those specific skills would matter less. They were wrong that they themselves would matter less. The abstraction raised the ceiling on what a skilled engineer could build, every single time.&lt;/p&gt;

&lt;p&gt;We are at that inflection point again. The syntax tax is not fully gone. But it is low enough that the verification tax is now the dominant cost. The bottleneck is no longer implementation capacity. It is specification quality, architectural judgment, and the ability to evaluate whether a system that can generate enormous volumes of code is generating the right enormous volumes of code.&lt;/p&gt;

&lt;p&gt;The factory model, the conductor-orchestrator split, context engineering: these are not replacements for engineering skill. They are the next abstraction layer. The developers who learn to work at this layer will build things that developers who stay below it simply cannot.&lt;/p&gt;

&lt;p&gt;Generation is solved. Verification, judgment, and direction are the new craft.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Osmani, A., Saboo, S., Kartakis, S. "The New SDLC with Vibe Coding." Google, May 2026. (Attached PDF)&lt;/li&gt;
&lt;li&gt;Karpathy, A. "Vibe Coding." X/Twitter post, February 2025. &lt;a href="https://x.com/karpathy/status/1886192184808149383" rel="noopener noreferrer"&gt;https://x.com/karpathy/status/1886192184808149383&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Analytics Drift. "Karpathy Declares Vibe Coding Obsolete, Introduces Agentic Engineering at Sequoia AI Ascent 2026." May 2026. &lt;a href="https://analyticsdrift.com/andrej-karpathy-agentic-engineering-software-3/" rel="noopener noreferrer"&gt;https://analyticsdrift.com/andrej-karpathy-agentic-engineering-software-3/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;METR. "Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity." July 2025. &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;METR. "Uplift Update: Measuring the Impact of AI Coding Tools." February 2026. &lt;a href="https://metr.org/blog/2026-02-24-uplift-update/" rel="noopener noreferrer"&gt;https://metr.org/blog/2026-02-24-uplift-update/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Index.dev. "Top 100 Developer Productivity Statistics with AI Tools 2026." &lt;a href="https://www.index.dev/blog/developer-productivity-statistics-with-ai-tools" rel="noopener noreferrer"&gt;https://www.index.dev/blog/developer-productivity-statistics-with-ai-tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modall. "AI in Software Development: 25+ Trends and Statistics (2026)." &lt;a href="https://modall.ca/blog/ai-in-software-development-trends-statistics" rel="noopener noreferrer"&gt;https://modall.ca/blog/ai-in-software-development-trends-statistics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MIT Technology Review. "AI Coding Is Now Everywhere. But Not Everyone Is Convinced." January 2026. &lt;a href="https://www.technologyreview.com/2025/12/15/1128352/rise-of-ai-coding-developers-2026/" rel="noopener noreferrer"&gt;https://www.technologyreview.com/2025/12/15/1128352/rise-of-ai-coding-developers-2026/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IBM. "What Is Agentic Engineering?" April 2026. &lt;a href="https://www.ibm.com/think/topics/agentic-engineering" rel="noopener noreferrer"&gt;https://www.ibm.com/think/topics/agentic-engineering&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The New Stack. "From Vibes to Engineering: How AI Agents Outgrew Their Own Terminology." February 2026. &lt;a href="https://thenewstack.io/vibe-coding-agentic-engineering/" rel="noopener noreferrer"&gt;https://thenewstack.io/vibe-coding-agentic-engineering/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Glide Blog. "What Is Agentic Engineering? How AI Engineering Has Evolved Past Vibe Coding in 2026." February 2026. &lt;a href="https://www.glideapps.com/blog/what-is-agentic-engineering" rel="noopener noreferrer"&gt;https://www.glideapps.com/blog/what-is-agentic-engineering&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Augment Code. "Why AI Coding Tools Make Experienced Developers 19% Slower and How to Fix It." January 2026. &lt;a href="https://www.augmentcode.com/guides/why-ai-coding-tools-make-experienced-developers-19-slower-and-how-to-fix-it" rel="noopener noreferrer"&gt;https://www.augmentcode.com/guides/why-ai-coding-tools-make-experienced-developers-19-slower-and-how-to-fix-it&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Osmani, A. "The Factory Model." &lt;a href="https://addyosmani.com/blog/factory-model/" rel="noopener noreferrer"&gt;https://addyosmani.com/blog/factory-model/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Second Talent. "84% of Developers Use AI Tools. Productivity Gains Are Only 10%. Here's Why." May 2026. &lt;a href="https://www.secondtalent.com/resources/ai-developer-productivity-tools-2026/" rel="noopener noreferrer"&gt;https://www.secondtalent.com/resources/ai-developer-productivity-tools-2026/&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;Sayed Ali Alkamel is a Google Developer Expert in Dart and Flutter, co-founder of Flutter MENA, and Manager of Digital Application Platforms at Oman Housing Bank. He has spoken at tech events across 22+ countries and shipped apps with 2.5M+ downloads. He writes about Flutter, AI, and the developer experience at dev.to/sayed_ali_alkamel.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>flutter</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Is Token Usage the New Lines of Code? How to Measure Developer Productivity in the AI Age</title>
      <dc:creator>Sayed Ali Alkamel</dc:creator>
      <pubDate>Tue, 16 Jun 2026 14:08:28 +0000</pubDate>
      <link>https://dev.to/sayed_ali_alkamel/is-token-usage-the-new-lines-of-code-how-to-measure-developer-productivity-in-the-ai-age-nd8</link>
      <guid>https://dev.to/sayed_ali_alkamel/is-token-usage-the-new-lines-of-code-how-to-measure-developer-productivity-in-the-ai-age-nd8</guid>
      <description>&lt;h2&gt;
  
  
  Is Token Usage the New Lines of Code? How to Measure Developer Productivity in the AI Age
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Whatever gets measured gets gamed." — Goodhart's Law&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An OpenAI engineer processed 210 billion tokens in a single week. A Claude Code user spent over $150,000 in a month. Salesforce quietly set a $175 minimum monthly token-spend target for engineers, and developers started asking AI to summarize documents they already understood, just to hit the number.&lt;/p&gt;

&lt;p&gt;Welcome to &lt;strong&gt;tokenmaxxing&lt;/strong&gt;. The newest chapter in software engineering's oldest and most embarrassing story: measuring the wrong thing, confidently, at scale.&lt;/p&gt;

&lt;p&gt;This article will give you the unfiltered answer to a question every engineering leader and developer is quietly debating right now. Is measuring token usage the same as measuring lines of code? And if so, what should we actually be measuring?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Ghost We Thought We Buried: Lines of Code
&lt;/h2&gt;

&lt;p&gt;Fred Brooks knew it in 1975. In &lt;em&gt;The Mythical Man-Month&lt;/em&gt;, he made it plain that adding more programmers to a late project makes it later. Productivity in software is not linear, it is not additive, and it cannot be reduced to volume.&lt;/p&gt;

&lt;p&gt;And yet, for decades, companies fell for the simplest metric available: lines of code (LOC). Count the output, reward the output, get more output. What they actually got was bloated codebases, unnecessary complexity, and engineers who learned to pad their work to look busy.&lt;/p&gt;

&lt;p&gt;Kent Beck, creator of Extreme Programming, said it directly: &lt;strong&gt;"The way you get programmer productivity is not by increasing the lines of code per programmer per day. That does not work. The way you get programmer productivity is by eliminating lines of code you have to write."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The industry eventually accepted this. LOC as a metric became a cautionary tale. We moved on to commits, pull requests, story points, velocity, cycle time. Every single one of these was gamed the moment it became a target. Agile teams discovered that story points originally meant as capacity planning proxies had transformed into performance theater.&lt;/p&gt;

&lt;p&gt;Then AI arrived. And the industry, with astonishing speed, invented a brand new broken metric.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tokenmaxxing: Lines of Code in a Lab Coat
&lt;/h2&gt;

&lt;p&gt;Token budgets are the new LOC. The logic sounds compelling at first: if a developer is using more AI compute, they must be building more things, right?&lt;/p&gt;

&lt;p&gt;Wrong. Measuring token consumption as a proxy for productivity makes the same fundamental error as counting lines of code. It measures an &lt;strong&gt;input to the process&lt;/strong&gt;, not the output, and certainly not the outcome.&lt;/p&gt;

&lt;p&gt;TechCrunch's April 2026 investigation into tokenmaxxing found that engineers with the largest token budgets produced the most pull requests, but the productivity improvement did not scale. &lt;strong&gt;They achieved two times the throughput at ten times the cost of tokens.&lt;/strong&gt; The tools were generating volume, not value.&lt;/p&gt;

&lt;p&gt;Faros AI drew on two years of customer data and found that code churn, lines of code deleted versus lines added, had increased &lt;strong&gt;861% under high AI adoption&lt;/strong&gt;. GitClear's January 2026 report found that regular AI users averaged &lt;strong&gt;9.4x higher code churn&lt;/strong&gt; than their non-AI counterparts, more than double the productivity gains the tools provided.&lt;/p&gt;

&lt;p&gt;More code is being written. Most of it is not sticking.&lt;/p&gt;

&lt;p&gt;Alex Circei, CEO of Waydev, which tracks developer analytics across more than 10,000 engineers, put the problem precisely: engineering managers are seeing code acceptance rates of 80 to 90 percent, but they are missing the churn that happens when engineers have to revise that accepted code in the following weeks, which drives the real-world acceptance rate down to between 10 and 30 percent of generated code.&lt;/p&gt;

&lt;p&gt;Token usage measures how aggressively a developer consumes AI compute. It says nothing about whether the output shipped, whether it held up in production, whether it was the right thing to build, or whether a senior engineer would need to rewrite it three weeks later.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Research Actually Says (and It Is Uncomfortable)
&lt;/h2&gt;

&lt;p&gt;The METR randomized controlled trial from July 2025 tested 16 experienced open-source developers across 246 real tasks. The result shocked the industry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developers using AI tools completed tasks 19% slower than the control group.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The more alarming part: before the study, developers predicted AI would speed them up by 24%. After completing it, they still estimated they had been 20% faster, despite objective measurement proving the opposite. The perception gap was total.&lt;/p&gt;

&lt;p&gt;This is not an argument against AI tools. The same period saw Google's 2025 DORA report show that AI adoption correlates with higher software delivery throughput for teams that have invested in strong engineering foundations first. The difference lies in &lt;em&gt;how&lt;/em&gt; AI is being used and, critically, &lt;em&gt;how productivity is being measured&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Martin Fowler, Chief Scientist at Thoughtworks and one of the most influential voices in software engineering, wrote in August 2025 that most LLM usage in the industry is "fancy auto-complete," and that the developers getting the most value are those who allow AI to directly read and edit source code files, not just suggest snippets. Fowler has long argued that measuring individual developer productivity is a "fool's errand," and the AI era has only deepened that conviction. Optimizing for the speed of code production, he argues, misses the point entirely, much like measuring a novelist's productivity by words per minute rather than by the quality of the narrative.&lt;/p&gt;

&lt;p&gt;Nicole Forsgren, creator of DORA and SPACE, and author of &lt;em&gt;Accelerate&lt;/em&gt;, said it plainly in late 2025: &lt;strong&gt;"AI broke our developer productivity metrics. Lines of code? Meaningless. Commits? Not the point. Velocity? Can be misleading. We need new frameworks for measuring DevEx in the age of AI."&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  So What Should You Actually Measure?
&lt;/h2&gt;

&lt;p&gt;The answer is not a single number. It never was. Here are the frameworks and signals that hold up:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Business Outcomes, Not Activity Proxies
&lt;/h3&gt;

&lt;p&gt;The only honest question is: did the software deliver value? Did the feature ship and work? Did it reduce support tickets, increase revenue, or improve retention? These are hard to measure in a sprint, which is exactly why organizations reach for activity proxies. Resist it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Code Durability
&lt;/h3&gt;

&lt;p&gt;Instead of counting how many lines were written or tokens consumed, track how much of that code survives. Code durability, the ratio of code that stays in the codebase after 30, 60, and 90 days, is a far more honest signal of whether the work was good. High churn under AI adoption is a red flag that should be on every engineering dashboard in 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The SPACE Framework
&lt;/h3&gt;

&lt;p&gt;Developed by Nicole Forsgren and colleagues from GitHub and Microsoft Research, SPACE measures five dimensions simultaneously: &lt;strong&gt;S&lt;/strong&gt;atisfaction and well-being, &lt;strong&gt;P&lt;/strong&gt;erformance, &lt;strong&gt;A&lt;/strong&gt;ctivity, &lt;strong&gt;C&lt;/strong&gt;ommunication and collaboration, and &lt;strong&gt;E&lt;/strong&gt;fficiency and flow. The key insight is that no single dimension tells the full story. A team shipping more PRs but burning out is not a productive team.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. DORA Metrics (With AI-Era Adjustments)
&lt;/h3&gt;

&lt;p&gt;Deployment Frequency, Lead Time for Changes, Change Failure Rate, and Mean Time to Restore remain the gold standard for measuring the health of your delivery pipeline. In the AI era, Change Failure Rate and Mean Time to Restore are more important than ever, because the speed gains from AI mean you can ship broken code faster too. Add AI attribution and complexity-adjusted throughput to see the full picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Specification Quality
&lt;/h3&gt;

&lt;p&gt;Here is the new skill nobody is measuring yet. In the Agentic Engineering era that Andrej Karpathy described at AI Ascent 2026, the developer's primary output is increasingly the specification, not the code. &lt;strong&gt;The human role shifts from writing code to owning the spec, design, and judgment calls.&lt;/strong&gt; A developer who writes a rigorous, well-scoped specification that agents can execute correctly is more productive than one who burns tokens on vague prompts and spends days fixing hallucinated output.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Skills That Actually Matter Now
&lt;/h2&gt;

&lt;p&gt;Kent Beck said something quietly devastating: &lt;strong&gt;"90% of my skills are now worth $0. But the other 10% are worth 1000x."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That 10% is not typing speed. It is not syntax memorization. It is not the ability to recall a library API from memory.&lt;/p&gt;

&lt;p&gt;The skills that compound in the AI age are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem formulation.&lt;/strong&gt; The ability to articulate what needs to be built clearly enough that an agent can execute it without ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural judgment.&lt;/strong&gt; You can outsource implementation to agents. You cannot outsource the ability to catch a subtle logical error or a design decision that will haunt the codebase for years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent orchestration.&lt;/strong&gt; Running parallel agents, reviewing their outputs critically, understanding when to trust and when to reject. This is the new technical skill floor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context curation.&lt;/strong&gt; Knowing what context to give an AI and what to leave out. A developer who extracts the 20 relevant lines from a 10,000-line codebase and frames the right question consistently outperforms one who pastes the entire repo and hopes for the best.&lt;/p&gt;

&lt;p&gt;None of these skills show up in a token count.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;Is measuring token usage the same as measuring lines of code?&lt;/p&gt;

&lt;p&gt;Yes. Structurally, they are the same mistake.&lt;/p&gt;

&lt;p&gt;Both measure an input to the process instead of the output. Both are easily gamed once they become a target. Both create perverse incentives: write more code, burn more tokens, look productive while potentially reducing the quality and maintainability of the system. And both measure the wrong end of the value chain entirely.&lt;/p&gt;

&lt;p&gt;The engineers doing the most valuable work in 2026 might be consuming far fewer tokens than their tokenmaxxing colleagues. They might be spending hours in a whiteboard session killing a bad idea before a single prompt is written. They might be writing a specification so precise that an agent executes the entire feature on the first pass. That work is nearly invisible to any activity-based metric.&lt;/p&gt;

&lt;p&gt;Goodhart's Law will always win. When token spend becomes a target, it will cease to be a good measure. It will just become the new leaderboard. And we will spend another decade learning the same lesson Fred Brooks already taught us in 1975.&lt;/p&gt;

&lt;p&gt;The question is not how much AI compute a developer burns. The question is whether the software ships, holds up, and matters.&lt;/p&gt;

&lt;p&gt;Everything else is noise.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Martin Fowler, &lt;em&gt;Some thoughts on LLMs and Software Development&lt;/em&gt;, martinfowler.com, August 2025&lt;/li&gt;
&lt;li&gt;Becker et al., &lt;em&gt;Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity&lt;/em&gt;, METR / arXiv:2507.09089, July 2025&lt;/li&gt;
&lt;li&gt;Tim Fernholz, &lt;em&gt;Tokenmaxxing is making developers less productive than they think&lt;/em&gt;, TechCrunch, April 2026&lt;/li&gt;
&lt;li&gt;Nicole Forsgren, &lt;em&gt;Frictionless: Seven Steps to Help Engineering Teams Move Faster in the Age of AI&lt;/em&gt;, 2025&lt;/li&gt;
&lt;li&gt;Faros AI, &lt;em&gt;AI Acceleration Whiplash&lt;/em&gt;, March 2026&lt;/li&gt;
&lt;li&gt;GitClear, &lt;em&gt;Developer Cohort Analysis: AI Coding Output&lt;/em&gt;, January 2026&lt;/li&gt;
&lt;li&gt;Andrej Karpathy at Sequoia Capital AI Ascent 2026, karpathy.bearblog.dev&lt;/li&gt;
&lt;li&gt;Kent Beck, &lt;em&gt;TDD, AI agents and coding&lt;/em&gt;, The Pragmatic Engineer, July 2025&lt;/li&gt;
&lt;li&gt;Gergely Orosz, &lt;em&gt;How AI is changing Software Engineering&lt;/em&gt;, The Pragmatic Engineer, April 2026&lt;/li&gt;
&lt;li&gt;Fred P. Brooks Jr., &lt;em&gt;The Mythical Man-Month: Essays on Software Engineering&lt;/em&gt;, 1975&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Sayed Ali Alkamel is Manager of Digital Application Platforms at Oman Housing Bank, a Google Developer Expert in Dart and Flutter, and co-founder of Flutter MENA. He ships production systems at the intersection of fintech, mobile, and AI infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>programming</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
