<?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: Google Developer Experts</title>
    <description>The latest articles on DEV Community by Google Developer Experts (gde).</description>
    <link>https://dev.clauneck.workers.dev/gde</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%2Forganization%2Fprofile_image%2F11939%2Fe3080d5b-ecde-42a8-b089-bafecc31fa97.png</url>
      <title>DEV Community: Google Developer Experts</title>
      <link>https://dev.clauneck.workers.dev/gde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.clauneck.workers.dev/feed/gde"/>
    <language>en</language>
    <item>
      <title>Extending a Rust MCP/A2A Currency Agent with A2UI</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Wed, 24 Jun 2026 01:34:55 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/extending-a-rust-mcpa2a-currency-agent-with-a2ui-22jc</link>
      <guid>https://dev.clauneck.workers.dev/gde/extending-a-rust-mcpa2a-currency-agent-with-a2ui-22jc</guid>
      <description>&lt;h4&gt;
  
  
  Building an Agent with Rust,A2A, MCP, and A2UI
&lt;/h4&gt;

&lt;p&gt;This tutorial aims to extend and test a currency Agent using A2A and MCP protocols with the A2UI protocol for custom presentation.&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%2Fk63aazqsnk890gidtnhr.jpeg" 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%2Fk63aazqsnk890gidtnhr.jpeg" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Reduce, Re-Use, Re-Cycle!
&lt;/h4&gt;

&lt;p&gt;This paper is a re-visiting of the original currency Agent Codelab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codelabs.developers.google.com/codelabs/currency-agent#0" rel="noopener noreferrer"&gt;Getting Started with MCP, ADK and A2A | Google Codelabs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and a GitHub Repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jackwotherspoon/currency-agent" rel="noopener noreferrer"&gt;GitHub - jackwotherspoon/currency-agent: A sample agent demonstrating A2A + ADK + MCP working together.&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Didn’t you already do this?
&lt;/h4&gt;

&lt;p&gt;You betcha. This was deployed in Python:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/extending-a-mcp-a2a-currency-agent-with-a2ui-dc43ffdc2508" rel="noopener noreferrer"&gt;Extending a MCP/A2A Currency Agent with A2UI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this updated version, the Antigravity CLI is used to add support for A2UI and extend the existing user interface and redeploy the agents in Rust.&lt;/p&gt;

&lt;p&gt;All of these Agent protocols are language neutral and a key strength of the protocols is the ability to implement a solution in any programming language that supports the specification.&lt;/p&gt;

&lt;h4&gt;
  
  
  What is the A2A protocol?
&lt;/h4&gt;

&lt;p&gt;The Agent2Agent (A2A) protocol, an open communication standard for AI agents, was initially introduced by Google in April 2025. It is specifically engineered to facilitate seamless interoperability within multi-agent systems, enabling AI agents developed by diverse providers or built upon disparate AI agent frameworks to communicate and collaborate effectively.&lt;/p&gt;

&lt;p&gt;A good overview of the A2A protocol can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://a2a-protocol.org/latest/" rel="noopener noreferrer"&gt;A2A Protocol&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Language Support For the A2A Protocol
&lt;/h4&gt;

&lt;p&gt;The official ADK for Python, GO, and Java provide built-in support for working with the A2A protocol. For other programming languages like JS, Rust, and .NET — 3rd party libraries are available to add support for the protocol.&lt;/p&gt;

&lt;p&gt;The main source for A2A Language support is the GitHub repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/a2aproject/A2A" rel="noopener noreferrer"&gt;GitHub - a2aproject/A2A: An open protocol enabling communication and interoperability between opaque agentic applications.&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  A2UI
&lt;/h4&gt;

&lt;p&gt;A2UI (Agent-to-User Interface) is an open-source protocol that allows AI agents to dynamically generate and stream rich, interactive user interfaces in real-time. [&lt;a href="https://a2ui.org/introduction/what-is-a2ui/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://www.copilotkit.ai/blog/build-with-googles-new-a2ui-spec-agent-user-interfaces-with-a2ui-ag-ui" rel="noopener noreferrer"&gt;2&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Instead of an AI relying on pre-built screens or just returning plain text in a chat window, A2UI enables the agent to instantly build and display tailored components like interactive charts, date-pickers, or approval forms. [&lt;a href="https://a2ui.org/introduction/what-is-a2ui/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://www.copilotkit.ai/blog/build-with-googles-new-a2ui-spec-agent-user-interfaces-with-a2ui-ag-ui" rel="noopener noreferrer"&gt;2&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;&lt;a href="https://a2ui.org/introduction/what-is-a2ui/" rel="noopener noreferrer"&gt;What is A2UI? - A2UI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  More Word Salad Protocols — What about A2A-XYZ?
&lt;/h4&gt;

&lt;p&gt;This article provides a good overview of how the various protocols fit together:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@visrow/a2a-mcp-ag-ui-a2ui-the-essential-2026-ai-agent-protocol-stack-ee0e65a672ef" rel="noopener noreferrer"&gt;A2A, MCP, AG-UI, A2UI: The Essential 2026 AI Agent Protocol Stack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Confused yet? But wait- there’s more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://a2ui.org/introduction/agent-ui-ecosystem/" rel="noopener noreferrer"&gt;How Does A2UI Compare? - A2UI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Checking the Developer Environment
&lt;/h4&gt;

&lt;p&gt;Verify that all the prerequisite packages and compilers are installed — and clone the sample Github repo:&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/xbill9/currency-agent-rust
&lt;span class="nb"&gt;cd &lt;/span&gt;currency-agent-rust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have your Google Cloud Project and preferred authentication method — run the &lt;strong&gt;init.sh&lt;/strong&gt; script to validate the setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/currency-agent-rust&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;set_env.sh&lt;/strong&gt; script is provided to set common environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/currency-agent-rust&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh 
Current Environment
&lt;span class="nv"&gt;GOOGLE_GENAI_USE_VERTEXAI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
&lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_CLOUD_PROJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;comglitn
&lt;span class="nv"&gt;GOOGLE_CLOUD_LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1
&lt;span class="nv"&gt;GENAI_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini-2.5-flash"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Debugging API Permission Errors
&lt;/h4&gt;

&lt;p&gt;If your application default credentials expires or your Google Cloud Authentication expires you will get an error. The workaround is to re-authenticate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud auth login
gcloud auth application-default login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another common error is that the environment variables are not set correctly. Go the the root directory and re-run the &lt;strong&gt;set_env.sh&lt;/strong&gt; to set the variables:&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="nb"&gt;cd&lt;/span&gt; ~/currency-agent-rust
&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  A2A Debugging Tools -A2A Inspector
&lt;/h4&gt;

&lt;p&gt;The A2A Inspector is a standalone tool that provides low level visibility into the A2A protocol. The GitHub is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/a2aproject/a2a-inspector" rel="noopener noreferrer"&gt;GitHub - a2aproject/a2a-inspector: Validation Tools for A2A Agents&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A summary of the features of the A2A inspector can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://a2aprotocol.ai/docs/guide/a2a-inspector" rel="noopener noreferrer"&gt;A2A Protocol Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To install the A2A Inspector:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/a2aproject/a2a-inspector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then follow the build instructions — you need &lt;strong&gt;&lt;em&gt;uv&lt;/em&gt;&lt;/strong&gt; , and a recent version of  &lt;strong&gt;&lt;em&gt;node&lt;/em&gt;&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/a2aproject/a2a-inspector/blob/main/README.md" rel="noopener noreferrer"&gt;a2a-inspector/README.md at main · a2aproject/a2a-inspector&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Staring the A2A Inspector
&lt;/h4&gt;

&lt;p&gt;Once the A2A inspector has been installed- you can validate the installation by using this URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://127.0.0.1:5001/" rel="noopener noreferrer"&gt;http://127.0.0.1:5001/&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Local A2A Validation with A2A Inspector
&lt;/h4&gt;

&lt;p&gt;The local A2A agent is checked with the A2A Inspector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;a2a&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;listening&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;

&lt;span class="mf"&gt;112.0&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;913&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="n"&gt;thinking&lt;/span&gt;
&lt;span class="err"&gt;●&lt;/span&gt; &lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;xbill&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rust&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;Rust&lt;/span&gt; &lt;span class="nf"&gt;A2A&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;configured&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="n"&gt;specified&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt;
  &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.

  This&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;defined&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u16&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then the Agent card is retrieved:&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%2Fb1dc0il9xf7jsuxzh9m0.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%2Fb1dc0il9xf7jsuxzh9m0.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the Agent skill cross checked:&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%2F6dxqxh78n1ns51nnv9r5.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%2F6dxqxh78n1ns51nnv9r5.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI with MCP Connection
&lt;/h4&gt;

&lt;p&gt;The Master Agent starts a FastMCP server interface. This allows Antigravity CLI to be used as a MCP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;xbill@penguin:~/currency-agent-rust/.agents$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mcp_config.json&lt;/span&gt;&lt;span class="w"&gt; 

    &lt;/span&gt;&lt;span class="nl"&gt;"currency_mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"serverUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/mcp"&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;The MCP tools can be reviewed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
   ✓ currency_mcp Tools: get_exchange_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP tool functionality can be checked with Antigravity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what does get_exchange_rate do?

1.4k in | 125 out | 40 thinking
● Read(/home/xbill/currency-agent-rust/mcp-server/src/main.rs) (ctrl+o to expand)

  The get_exchange_rate tool is exposed by the Model Context Protocol (MCP) server to fetch current or historical
  currency exchange rates.

  ### What it does

  It fetches the exchange rate to convert a base currency to a target currency for a given date (or the latest available
  rate). Under the hood, it queries the Frankfurter API ( https://api.frankfurter.dev/v1/{currency_date} ).

  ### Parameters

  The tool accepts three arguments:

  • currency_from (string, default: "USD" ): The currency to convert from (e.g., "USD" , "GBP" ).
  • currency_to (string, default: "EUR" ): The currency to convert to (e.g., "EUR" , "JPY" ).
  • currency_date (string, default: "latest" ): A specific date for historical rates (e.g., "2025-10-15" ) or
  "latest" for the most recent rate.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  So What is all this Doing?
&lt;/h4&gt;

&lt;p&gt;All of the setup and configuration allows you to start debugging and visualizing Agent flows using the A2A protocol. The currency Agent has been validated using the A2A Inspector and the MCP interface via Antigravity CLI.&lt;/p&gt;

&lt;p&gt;The make help provides the main project targets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;xbill@penguin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;~/currency-agent$ make help&lt;/span&gt;
&lt;span class="nl"&gt;Available commands&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="err"&gt;install&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Install&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;project&lt;/span&gt; &lt;span class="err"&gt;dependencies&lt;/span&gt; &lt;span class="err"&gt;(including&lt;/span&gt; &lt;span class="err"&gt;frontend&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;react&lt;/span&gt; &lt;span class="err"&gt;UI)&lt;/span&gt;
  &lt;span class="err"&gt;start&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Start&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;services&lt;/span&gt; &lt;span class="err"&gt;in&lt;/span&gt; &lt;span class="err"&gt;background&lt;/span&gt; &lt;span class="err"&gt;(MCP&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="err"&gt;Agent)&lt;/span&gt;
  &lt;span class="err"&gt;stop&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Stop&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;background&lt;/span&gt; &lt;span class="err"&gt;services&lt;/span&gt;
  &lt;span class="err"&gt;status&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Check&lt;/span&gt; &lt;span class="err"&gt;status&lt;/span&gt; &lt;span class="err"&gt;of&lt;/span&gt; &lt;span class="err"&gt;background&lt;/span&gt; &lt;span class="err"&gt;services&lt;/span&gt;
  &lt;span class="err"&gt;mcp&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Start&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;MCP&lt;/span&gt; &lt;span class="err"&gt;Server&lt;/span&gt; &lt;span class="err"&gt;(foreground)&lt;/span&gt;
  &lt;span class="err"&gt;agent&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Start&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;A2A&lt;/span&gt; &lt;span class="err"&gt;Agent&lt;/span&gt; &lt;span class="err"&gt;Server&lt;/span&gt; &lt;span class="err"&gt;(foreground)&lt;/span&gt;
  &lt;span class="err"&gt;frontend&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Build&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;start&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;FastAPI&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="err"&gt;Vanilla&lt;/span&gt; &lt;span class="err"&gt;TS&lt;/span&gt; &lt;span class="err"&gt;frontend&lt;/span&gt; &lt;span class="err"&gt;server&lt;/span&gt; &lt;span class="err"&gt;(port&lt;/span&gt; &lt;span class="err"&gt;8000)&lt;/span&gt;
  &lt;span class="err"&gt;react-install&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Install&lt;/span&gt; &lt;span class="err"&gt;dependencies&lt;/span&gt; &lt;span class="err"&gt;for&lt;/span&gt; &lt;span class="err"&gt;React&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="err"&gt;CopilotKit&lt;/span&gt; &lt;span class="err"&gt;UI&lt;/span&gt;
  &lt;span class="err"&gt;react-ui&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Start&lt;/span&gt; &lt;span class="err"&gt;React&lt;/span&gt; &lt;span class="err"&gt;Frontend&lt;/span&gt; &lt;span class="err"&gt;UI&lt;/span&gt; &lt;span class="err"&gt;(port&lt;/span&gt; &lt;span class="err"&gt;3000)&lt;/span&gt;
  &lt;span class="err"&gt;react-agent&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Start&lt;/span&gt; &lt;span class="err"&gt;React&lt;/span&gt; &lt;span class="err"&gt;Frontend&lt;/span&gt; &lt;span class="err"&gt;Agent&lt;/span&gt; &lt;span class="err"&gt;(port&lt;/span&gt; &lt;span class="err"&gt;8000)&lt;/span&gt;
  &lt;span class="err"&gt;test-client&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;A2A&lt;/span&gt; &lt;span class="err"&gt;Client&lt;/span&gt; &lt;span class="err"&gt;(test&lt;/span&gt; &lt;span class="err"&gt;queries)&lt;/span&gt;
  &lt;span class="err"&gt;e2e-test&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;end-to-end&lt;/span&gt; &lt;span class="err"&gt;tests&lt;/span&gt; &lt;span class="err"&gt;(alias&lt;/span&gt; &lt;span class="err"&gt;for&lt;/span&gt; &lt;span class="err"&gt;test-client)&lt;/span&gt;
  &lt;span class="err"&gt;adktest&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;interactive&lt;/span&gt; &lt;span class="err"&gt;ADK&lt;/span&gt; &lt;span class="err"&gt;CLI&lt;/span&gt; &lt;span class="err"&gt;for&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;agent&lt;/span&gt;
  &lt;span class="err"&gt;test&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;all&lt;/span&gt; &lt;span class="err"&gt;tests&lt;/span&gt; &lt;span class="err"&gt;(pytest)&lt;/span&gt;
  &lt;span class="err"&gt;frontend-test&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;frontend&lt;/span&gt; &lt;span class="err"&gt;specific&lt;/span&gt; &lt;span class="err"&gt;tests&lt;/span&gt;
  &lt;span class="err"&gt;lint&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;linting&lt;/span&gt; &lt;span class="err"&gt;checks&lt;/span&gt; &lt;span class="err"&gt;(ruff)&lt;/span&gt;
  &lt;span class="err"&gt;format&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Auto-format&lt;/span&gt; &lt;span class="err"&gt;code&lt;/span&gt; &lt;span class="err"&gt;(ruff)&lt;/span&gt;
  &lt;span class="err"&gt;clean&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Remove&lt;/span&gt; &lt;span class="err"&gt;caches&lt;/span&gt; &lt;span class="err"&gt;and&lt;/span&gt; &lt;span class="err"&gt;logs&lt;/span&gt;
  &lt;span class="err"&gt;deploy&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Deploy&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;Cloud&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;using&lt;/span&gt; &lt;span class="err"&gt;Cloud&lt;/span&gt; &lt;span class="err"&gt;Build&lt;/span&gt;
  &lt;span class="err"&gt;logs&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Read&lt;/span&gt; &lt;span class="err"&gt;logs&lt;/span&gt; &lt;span class="err"&gt;from&lt;/span&gt; &lt;span class="err"&gt;Cloud&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt;
  &lt;span class="err"&gt;endpoint&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Get&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;Cloud&lt;/span&gt; &lt;span class="err"&gt;Run&lt;/span&gt; &lt;span class="err"&gt;service&lt;/span&gt; &lt;span class="err"&gt;endpoint&lt;/span&gt;
  &lt;span class="err"&gt;remote-status&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;Check&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;status&lt;/span&gt; &lt;span class="err"&gt;of&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;remote&lt;/span&gt; &lt;span class="err"&gt;endpoint&lt;/span&gt;
&lt;span class="nl"&gt;xbill@penguin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;~/currency-agent$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Building and Debugging
&lt;/h4&gt;

&lt;p&gt;The Makefile provides targets to build and manage the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Building Rust projects...
cargo build
    Finished &lt;span class="sb"&gt;`&lt;/span&gt;dev&lt;span class="sb"&gt;`&lt;/span&gt; profile &lt;span class="o"&gt;[&lt;/span&gt;unoptimized + debuginfo] target&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;0.07s
cargo build &lt;span class="nt"&gt;--manifest-path&lt;/span&gt; mcp-server/Cargo.toml
    Finished &lt;span class="sb"&gt;`&lt;/span&gt;dev&lt;span class="sb"&gt;`&lt;/span&gt; profile &lt;span class="o"&gt;[&lt;/span&gt;unoptimized + debuginfo] target&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;0.05s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lint:&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="o"&gt;&amp;gt;&lt;/span&gt; make lint

● Bash&lt;span class="o"&gt;(&lt;/span&gt;make lint&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ctrl+o to &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  I have executed make lint on your workspace. The output shows that all checks passed successfully:

    Running linting checks &lt;span class="o"&gt;(&lt;/span&gt;ruff check + format&lt;span class="o"&gt;)&lt;/span&gt;...
    uv run ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
    All checks passed!
    uv run ruff format &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
    20 files already formatted
    Running Rust linting checks &lt;span class="o"&gt;(&lt;/span&gt;clippy + &lt;span class="nb"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;...
    cargo clippy &lt;span class="nt"&gt;--all-targets&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; warnings
        Finished &lt;span class="sb"&gt;`&lt;/span&gt;dev&lt;span class="sb"&gt;`&lt;/span&gt; profile &lt;span class="o"&gt;[&lt;/span&gt;unoptimized + debuginfo] target&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;0.15s
    cargo clippy &lt;span class="nt"&gt;--manifest-path&lt;/span&gt; mcp-server/Cargo.toml &lt;span class="nt"&gt;--all-targets&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; warnings
        Finished &lt;span class="sb"&gt;`&lt;/span&gt;dev&lt;span class="sb"&gt;`&lt;/span&gt; profile &lt;span class="o"&gt;[&lt;/span&gt;unoptimized + debuginfo] target&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;0.10s
    cargo &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--check&lt;/span&gt;
    cargo &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;--manifest-path&lt;/span&gt; mcp-server/Cargo.toml &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and tested:&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="o"&gt;&amp;gt;&lt;/span&gt; make test-client

66.0k &lt;span class="k"&gt;in&lt;/span&gt; | 491 out | 296 thinking
● Bash&lt;span class="o"&gt;(&lt;/span&gt;make stop&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ctrl+o to &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  I have successfully executed the make test-client &lt;span class="nb"&gt;command &lt;/span&gt;using the newly updated Rust targets.

  Here is what occurred:

  1. Started Services: Started the Rust MCP server and Rust Currency Agent &lt;span class="k"&gt;in &lt;/span&gt;the background via make start &lt;span class="nb"&gt;.&lt;/span&gt;
  2. Executed Client: Ran make test-client which successfully compiled &lt;span class="o"&gt;(&lt;/span&gt;using the cached target&lt;span class="o"&gt;)&lt;/span&gt; and ran the Rust
  test_client &lt;span class="nb"&gt;.&lt;/span&gt;
  3. Verified Responses:
      • Retrieved the agent card from the Rust agent.
      • Performed a single-turn request &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"how much is 100 USD in CAD?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; and received the structured A2UI JSON payload
      with the conversion result &lt;span class="o"&gt;(&lt;/span&gt; 100 USD is equal to 141.87 CAD. &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
      • Performed a multi-turn request &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"how much is 100 USD?"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; and received the appropriate clarification prompt &lt;span class="o"&gt;(&lt;/span&gt; What
      currency would you like to convert USD to? &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
  4. Cleanup: Stopped the background servers cleanly using resources by cleanly running make stop to close background
  processes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Time to Start some Currency Arbitrage!
&lt;/h4&gt;

&lt;p&gt;The servers are started:&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;### Complete Rust Setup: How to Run It All&lt;/span&gt;

  You can start each piece of the stack &lt;span class="k"&gt;in &lt;/span&gt;separate terminals:

  1. Terminal 1: Start the MCP Server &lt;span class="o"&gt;(&lt;/span&gt;port 8080&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;cd &lt;/span&gt;mcp-server
    cargo run &lt;span class="nt"&gt;--bin&lt;/span&gt; mcp-server

  2. Terminal 2: Start the Currency Agent &lt;span class="o"&gt;(&lt;/span&gt;port 10000&lt;span class="o"&gt;)&lt;/span&gt;
    cargo run &lt;span class="nt"&gt;--bin&lt;/span&gt; currency-agent

  3. Terminal 3: Run the Test Client
    cargo run &lt;span class="nt"&gt;--bin&lt;/span&gt; test_client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and checked end-to-end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/currency-agent-rust&lt;span class="nv"&gt;$ &lt;/span&gt;make test-rust-e2e 
Running Rust end-to-end test...
make stop
make[1]: Entering directory &lt;span class="s1"&gt;'/home/xbill/currency-agent-rust'&lt;/span&gt;
Stopping servers...
make[1]: Leaving directory &lt;span class="s1"&gt;'/home/xbill/currency-agent-rust'&lt;/span&gt;
Starting Rust MCP Server &lt;span class="o"&gt;(&lt;/span&gt;port 8080&lt;span class="o"&gt;)&lt;/span&gt;...
Starting Rust Currency Agent &lt;span class="o"&gt;(&lt;/span&gt;port 10000&lt;span class="o"&gt;)&lt;/span&gt;...
Running Rust Test Client...
cargo run &lt;span class="nt"&gt;--bin&lt;/span&gt; test_client
    Finished &lt;span class="sb"&gt;`&lt;/span&gt;dev&lt;span class="sb"&gt;`&lt;/span&gt; profile &lt;span class="o"&gt;[&lt;/span&gt;unoptimized + debuginfo] target&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;0.07s
     Running &lt;span class="sb"&gt;`&lt;/span&gt;target/debug/test_client&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt; 🔄 Connecting to agent at http://127.0.0.1:10000... &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt; ✅ Connection successful. Agent Card retrieved: &lt;span class="nt"&gt;---&lt;/span&gt;
Agent Name: currency_agent

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;● Bash&lt;span class="o"&gt;(&lt;/span&gt;make start&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ctrl+o to &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  I have successfully started the background services using the make start command. Both the Rust MCP Server and the
  Rust Currency Agent are now running &lt;span class="k"&gt;in &lt;/span&gt;the background.

    make start

  &lt;span class="c"&gt;### Verification&lt;/span&gt;

  • Rust MCP Server has started &lt;span class="k"&gt;in &lt;/span&gt;the background &lt;span class="o"&gt;(&lt;/span&gt;logging to mcp.log &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
  • Rust Currency Agent has started &lt;span class="k"&gt;in &lt;/span&gt;the background &lt;span class="o"&gt;(&lt;/span&gt;logging to agent.log &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/currency-agent-rust&lt;span class="nv"&gt;$ &lt;/span&gt;make status
Checking status of background services...
  MCP Server: Running &lt;span class="o"&gt;(&lt;/span&gt;PID 644873 &lt;span class="o"&gt;)&lt;/span&gt; on port&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;: 8080
  A2A Agent Server: Running &lt;span class="o"&gt;(&lt;/span&gt;PID 644897 &lt;span class="o"&gt;)&lt;/span&gt; on port&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;: 10000
  Frontend Server: Stopped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Front-End
&lt;/h4&gt;

&lt;p&gt;At this point- all of the backend services have been updated from Python to Rust. The same React front-end is built — but in this version — it will call the Rust agents!&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="o"&gt;&amp;gt;&lt;/span&gt; make frontend-install

  I will run the &lt;span class="nb"&gt;command &lt;/span&gt;make frontend-install to ensure all frontend dependencies are fully installed.

● Bash&lt;span class="o"&gt;(&lt;/span&gt;make frontend-install&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ctrl+o to &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  The installation of the frontend dependencies was successful!

  You can now start the frontend at any &lt;span class="nb"&gt;time &lt;/span&gt;with:

    make frontend

────────────────────────────────────────────────────────────
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; make frontend

  I will run the &lt;span class="nb"&gt;command &lt;/span&gt;make frontend to compile the production assets and launch the Vite preview server.

● Bash&lt;span class="o"&gt;(&lt;/span&gt;make frontend&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ctrl+o to &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  The frontend build and preview server has been successfully started &lt;span class="k"&gt;in &lt;/span&gt;the background.

  The dashboard is live and available at:
  🌐 URL: http://localhost:4173/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How does this all fit Together?
&lt;/h4&gt;

&lt;p&gt;Some nice little boxes in an architecture diagram build it out:&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%2F6s3q5t7ou7kr6fhfwkk3.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%2F6s3q5t7ou7kr6fhfwkk3.png" width="784" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Start the Currency Session
&lt;/h4&gt;

&lt;p&gt;Once all the agents have been started — the web interface on port 8000 is connected with a standard web browser:&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.amazonaws.com%2Fuploads%2Farticles%2Fskjz5490ig4n36ds03qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fskjz5490ig4n36ds03qx.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new A2UI tools are presented:&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.amazonaws.com%2Fuploads%2Farticles%2Fvf4iujag30j4g7ztfzx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf4iujag30j4g7ztfzx0.png" width="628" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running the new A2UI tools — conversion card:&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.amazonaws.com%2Fuploads%2Farticles%2Fnmxgzoavaxniio4qrj9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmxgzoavaxniio4qrj9i.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rates Table:&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.amazonaws.com%2Fuploads%2Farticles%2Fhm31qpvxv01dxg7fkp9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhm31qpvxv01dxg7fkp9s.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Line Chart: 5 day trend:&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.amazonaws.com%2Fuploads%2Farticles%2Fdb0uuisxvm5wzdki67q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb0uuisxvm5wzdki67q0.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Multicurrency Wallet:&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.amazonaws.com%2Fuploads%2Farticles%2Fadev29evgd9grrs8qtdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadev29evgd9grrs8qtdi.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What does the A2UI Code Look Like?
&lt;/h4&gt;

&lt;p&gt;The A2UI generated code follows a well defined format:&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="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;multi-row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tabular&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(such&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;currency&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;comparison&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;tables),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;formats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;follows:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;market&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rates:&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;a&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;ui-json&amp;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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v0.9"&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_table"&lt;/span&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;"Table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"props"&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;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Currency Pair"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Current Rate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Daily Change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rows"&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="s2"&gt;"USD / EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.86103"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+0.31%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🟢 Stable"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"USD / GBP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.78450"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+0.25%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🟢 Upward"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"USD / JPY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"149.52"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-0.18%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🔴 Downward"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"USD / CAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.35420"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+0.12%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"🟢 Stable"&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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;/a&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;ui-json&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How does this all Work?
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI has the answer!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; summarize how the project uses A2-UI

  The Currency Agent project leverages A2-UI (Agent-to-User Interface) to elevate standard text/markdown chat responses
  into premium, interactive graphical components.
  Here is a summary of how the system implements and processes A2-UI:

    graph TD
        A[User Inputs Query] --&amp;gt; B[Vite Client UI Sandbox]
        B --&amp;gt;|Parse Query / Simulate Delay| C[Mock Client Parser app.ts]
        C --&amp;gt;|Generate a2ui-json XML markup| D[HTML Render Pipeline]
        D --&amp;gt;|Instantiate Glassmorphic Elements| E[Render Interactive Cards, Charts, &amp;amp; Tables]

  ### 1. Unified Interface Layouts ( &amp;lt;a2ui-json&amp;gt; )
  The project defines a rich UI vocabulary inside &amp;lt;a2ui-json&amp;gt; XML payloads. Instead of plain text output, the agent
  represents structural elements (such as columns, rows, cards, text blocks, and charts) as declarative component JSON
  trees containing id , type , parentId , and props .

  ### 2. Client-Side Rendering Sandbox

  Because we shifted the project to run client-side (removing any Python or FastAPI dependencies to respect the pure
  stack), the rendering engine is fully executed in the browser inside frontend/frontend/app.ts :

  • Regex Extraction: When a response is received, the script searches for the XML tags: /&amp;lt;a2ui-json&amp;gt;([\s\S]*?)&amp;lt;\/a2ui-
  json&amp;gt;/ .
  • Component Construction: It parses the JSON representation and loops through the elements to construct custom
  glassmorphic wrappers dynamically.
  • Component Catalog Support:
      • Card: Renders highly stylized premium glassmorphic content cards.
      • Table: Auto-formats comparison headers and cells with active status indicators (e.g., green/red market arrows).
      • LineChart: Dynamically draws canvas charts plotting currency trends over time using modern HSL gradients.
      • Rows / Columns: Supports custom flex layouts to stack balances or values symmetrically.

  ### 3. Interactive Sandbox Buttons

  The interface includes dedicated instant sandbox launchers (e.g. Demo 1: A2UI Conversion Card, Demo 2: A2UI Rates
  Table). Clicking these injects live &amp;lt;a2ui-json&amp;gt; layouts instantly, demonstrating how the interface responds and
  updates seamlessly in real time!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew. that’s a lot! What else is there?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; are you sure there is no Python? How can this all be running in Rust?!
  I am absolutely certain there is zero Python code active! All python files, virtual environments ( .venv ), and Python
  packages have been completely purged from this workspace.

  Instead, the entire system is built and run using a clean, compiled Rust backend coupled with a Vanilla TS frontend
  (compiled by Vite/Node.js).
  Here is exactly how the system runs entirely without Python:
  ──────
  ### 1. The Core Rust Backend Architecture
  The backend consists of two compiled, ultra-high-performance binaries written in native Rust:
  • The Rust MCP Server ( mcp-server/src/main.rs ):
  Rather than using Python's FastMCP, this is written using native Rust. It exposes the get_exchange_rate tool,
  connects to the live Frankfurter API via HTTP requests, and serves standard JSON-RPC endpoints.
  • The Rust Currency Agent ( src/main.rs ):
  This binary uses the official Rust API clients to connect directly to Google's Gemini 2.5 models. It manages user
  conversation context, coordinates requests, and connects as an MCP client directly to the Rust MCP Server on port 8080
  to perform rapid exchange rate queries.
  • The Rust Test Client ( src/bin/test_client.rs ):
  An end-to-end command-line tester that directly sends messages to the compiled Rust Agent to verify the compiled logic
  and tool executions.
  ──────
  ### 2. The Pure Frontend Architecture (Vite)

  Because we removed the Python-based FastAPI gateway, the frontend is now 100% serverless and client-side:

  • Vite Static Compilation:
  Vite compiles the TypeScript and styling in frontend/frontend down to super-lightweight, static HTML, CSS, and JS.
  • Vite Preview:
  When you run make frontend , Vite spins up a static preview server on port 4173 . There is no Python or Python-based
  middleman.
  • Client-Side Query-to-A2UI Parser:
  The system uses the client-side mock parser in app.ts. It intercepts user conversion and rate query inputs and
  translates them instantly into the beautiful &amp;lt;a2ui-json&amp;gt; component&amp;gt; layout blocks directly in the browser—providing
  a fast, zero-delay sandbox experience! 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  WHAT? No Python?!?!
&lt;/h4&gt;

&lt;p&gt;Are you sure? How about a code review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Here is a comprehensive code review of the Currency Agent (Rust + Frontend) project.
  ──────
   🔎 Project Code Review 
  ## 1. Architectural Highlights
  The decision to remove Python has transformed the repository into an exceptionally clean, high-performance, compiled
  multi-agent structure:
    currency-agent-rust/
    ├── mcp-server/ # 🦀 Compiled Rust MCP Server (Axum + SSE)
    ├── src/ # 🦀 Compiled Rust Currency Agent (Gemini-2.5 client)
    ├── frontend/frontend/ # ⚡ Pure Client-Side TS Dashboard (Vite)
    └── Makefile # 🛠️ Unified orchestration file
    ──────
  ## 2. Component-by-Component Review

  ### 🦀 A. Rust MCP Server ( mcp-server/src/main.rs )
  The MCP server is built using Axum, Tokio, and Serde to implement the official Model Context Protocol over Server-Sent
  Events (SSE).

  • Strengths:
      • Type-Safe Request Modeling: Employs robust Serde struct serialization ( ExchangeRateArgs , GenericRequest , 
      CallParams ) representing JSON-RPC 2.0 payloads elegantly.
      • Proper Async Stream Management: Utilizes tokio::sync::mpsc channels and 
      tokio_stream::wrappers::UnboundedReceiverStream to pipe events asynchronously over SSE headers.
      • Session Lifecycle: Successfully maps active connection sessions with an HTTP session-id mapping (
      RwLock&amp;lt;HashMap&amp;lt;String, Session&amp;gt;&amp;gt; ).
  • Observations / Enhancements:
      • Port Configuration: The server uses SocketAddr::from(([0, 0, 0, 0], port)) , which is highly secure and suitable
      for containerized/Docker deployment patterns.
  ### 🦀 B. Rust Currency Agent ( src/main.rs )

  The core agent binary coordinates client requests, holds memory, and invokes tools.

  • Strengths:
      • Gemini 2.5 Optimization: Employs efficient async invocation of gemini-2.5-flash model.
      • Robust Tool Integration: Implements correct asynchronous MCP connections, parsing JSON schemas returned by the
      server on port 8080 .
      • Compiled Speed: Bypassing a dynamic scripting interpreter cuts runtime latency dramatically, leading to sub-
      millisecond orchestration loops.

  ### ⚡ C. Client-Side TS Frontend ( frontend/frontend/ )
  The interface leverages modern Vanilla TypeScript, compiled down to optimized static assets by Vite.

  • Strengths:
      • Direct-to-A2UI Parser: app.ts uses a clean regular expression extraction structure that processes dynamic
      layout formats dynamically in the client's thread.
      • Premium Glassmorphic Styling: Leverages HSL color variables and backdrop blur properties to produce standard-
      setting widgets (Cards, Tables, Custom LineChart graphing elements) without the overhead of heavy third-party
      React libraries.
      • High Performance: Served directly via Vite preview ( make frontend ), reducing build sizes and server-side CPU
      footprints to near zero.

  ──────
  ## 3. Impact of the "No-Python" Refactoring

   Metric | Prior (Python/FastAPI Stack) | Current (Rust + Vite Stack) | Impact
  ----------------------|---------------------------------|----------------------------------|---------------------------
   Interpreter Overhead | Active Python runtime processes | Native executable running | Ultra-low CPU &amp;amp; RAM
                        | | directly on OS threads | overhead
   Startup / Init Time | ~2–4 seconds (venv loading, | &amp;lt;10 milliseconds | Instant execution
                        | import parsing) | |
   Dependency Footprint | ~500MB ( .venv , uv | &amp;lt;30MB (Native binary and | 94% reduction in size
                        | directories, packages) | compiled assets) |
   Port Conflicts | Prone to port conflicts across | Well-isolated Axum + Vite bounds | Seamless local run
                        | Python frameworks | | execution
  ──────
  ## 4. Conclusion &amp;amp; Recommendations

  The current codebase is in excellent shape:

  1. It is extremely clean, modern, and maintains high standard-compliant design.
  2. The elimination of Python has successfully decoupled the frontend, removing complex middlewares and letting the
  client-side rendering pipeline operate with zero network perform all the visual weight lifting natively.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The goal of the demo/article was to enable A2UI presentation controls on a functional agent supporting common protocols like A2A and MCP.&lt;/p&gt;

&lt;p&gt;The currency agent was chosen as a starting point and new A2UI presentation objects were added to the user interface with Antigravity CLI.&lt;/p&gt;

&lt;p&gt;A clean Rust implementation of the key Agent protocols was able to seamlessly work with the existing front end interface.&lt;/p&gt;

&lt;p&gt;Finally- the updated A2UI project integration was summarized, and code reviewed with Antigravity CLI.&lt;/p&gt;




</description>
      <category>rust</category>
      <category>antigravitycli</category>
      <category>mcps</category>
      <category>a2ui</category>
    </item>
    <item>
      <title>12B Gemma 4 Deployment with NVIDIA Blackwell 6000, QAT, MTP, and Antigravity CLI</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Tue, 23 Jun 2026 03:39:55 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/12b-gemma-4-deployment-with-nvidia-blackwell-6000-qat-mtp-and-antigravity-cli-3gn6</link>
      <guid>https://dev.clauneck.workers.dev/gde/12b-gemma-4-deployment-with-nvidia-blackwell-6000-qat-mtp-and-antigravity-cli-3gn6</guid>
      <description>&lt;p&gt;This article provides a step by step deployment guide for Gemma 4 to a Google Cloud Run hosted GPU enabled system,. A suite of Python MCP tools is built to simplify management of the vLLM hosted Gemma 4 deployment with Antigravity CLI.&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%2Fq5ytt0v8zmvx55q2aju6.jpeg" 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%2Fq5ytt0v8zmvx55q2aju6.jpeg" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is this project trying to Do?
&lt;/h4&gt;

&lt;p&gt;This project is a DevOps/SRE assistant that uses a Gemma 4 model hosted on Cloud Run with GPU. It provides tools to provision the Docker container and deploy the model, as well as for observability and performance testing.&lt;/p&gt;

&lt;p&gt;This project is similar to a previous project that targeted GPU hosted Gemma4 instances on GCP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/gemma-sre-self-hosted-vllm-infrastructure-agent-afda000c0943" rel="noopener noreferrer"&gt;Gemma-SRE: Self-Hosted vLLM Infrastructure Agent&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Installation Instructions
&lt;/h4&gt;

&lt;p&gt;The detailed installation instructions for Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Python MCP Documentation
&lt;/h4&gt;

&lt;p&gt;The official GitHub Repo provides samples and documentation for getting started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;GitHub - modelcontextprotocol/python-sdk: The official Python SDK for Model Context Protocol servers and clients&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Where do I start?
&lt;/h4&gt;

&lt;p&gt;The strategy for starting MCP development for model management is a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;First, the basic development environment is setup with the required system variables, and a working Antigravity CLI configuration.&lt;/p&gt;

&lt;p&gt;Then, a minimal Python MCP Server is built with stdio transport. This server is validated with Antigravity CLI in the local environment.&lt;/p&gt;

&lt;p&gt;This setup validates the connection from Antigravity CLI to the local server via MCP. The MCP client (Antigravity CLI) and the Python MCP server both run in the same local environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup the Basic Environment
&lt;/h4&gt;

&lt;p&gt;At this point you should have a working Python environment and a working Antigravity CLI installation. The next step is to clone the GitHub samples repository with support scripts:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/xbill9/gemma4-tips
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;init.sh&lt;/strong&gt; from the cloned directory.&lt;/p&gt;

&lt;p&gt;The script will attempt to determine your shell environment and set the correct variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpu-12B-qat-mtp-6000-devops-agent
source init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session times out or you need to re-authenticate- you can run the &lt;strong&gt;set_env.sh&lt;/strong&gt; script to reset your environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gpu-12B-qat-mtp-6000-devops-agent
source set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Management Tool with MCP Stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the standard MCP libraries provide is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;The connection over stdio will look similar to this:&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;# Initialize FastMCP server
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Self-Hosted vLLM DevOps Agent&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;h4&gt;
  
  
  Running the Python Code
&lt;/h4&gt;

&lt;p&gt;First- switch the directory with the Python version of the MCP sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/gemma4-tips/gpu-12B-qat-mtp-6000-devops-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the release version on the local system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;make&lt;/span&gt; &lt;span class="err"&gt;install&lt;/span&gt;
&lt;span class="err"&gt;Processing&lt;/span&gt; &lt;span class="err"&gt;./.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project can also be linted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/gpu-12B-qat-mtp-6000-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make lint
ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
All checks passed!
ruff format &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
6 files already formatted
mypy &lt;span class="nb"&gt;.&lt;/span&gt;
Success: no issues found &lt;span class="k"&gt;in &lt;/span&gt;6 &lt;span class="nb"&gt;source &lt;/span&gt;files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MCP stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the MCP protocol provides is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;In this project Antigravity CLI is used as the MCP client to interact with the Python MCP server code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI mcp_config.json
&lt;/h4&gt;

&lt;p&gt;A sample MCP server file is provided in the .agents directory:&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="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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"gpu-devops-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"/home/xbill/gemma4-tips/gpu-12B-qat-mtp-6000-devops-agent/server.py"&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;"env"&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;"GOOGLE_CLOUD_PROJECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aisprint-491218"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_CLOUD_LOCATION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-central1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"VLLM_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"MODEL_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/models/gemma-4-12B-it-qat-w4a16-ct"&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;h4&gt;
  
  
  Validation with Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;The final connection test uses Antigravity CLI as a MCP client with the Python code providing the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
&amp;gt; ✓ google-dev-knowledge Tools: search_documents, answer_query, get_documents
   ✓ gpu-devops-agent Tools: save_hf_token, get_vllm_endpoint, list_vertex_models, list_bucket_models,
                                analyze_cloud_logging, +19 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting Started with Gemma 4 on GPU
&lt;/h4&gt;

&lt;p&gt;The Official vLLM repo also has Gemma4 specific information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm/releases" rel="noopener noreferrer"&gt;Releases · vllm-project/vllm&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Lifecycle Management via MCP
&lt;/h4&gt;

&lt;p&gt;The MCP tools provide a complete suite of agent-oriented operations for managing vLLM deployment on Cloud Run or a TPU.&lt;/p&gt;

&lt;p&gt;The MCP server tools are used to deploy the model to Cloud Run with TPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy the model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Checking System status
&lt;/h4&gt;

&lt;p&gt;The status can be checked with an MCP tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;  ### 🧩 Model Details

  Endpoint URL: https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app 

  Model Information ( /v1/models ):&lt;span class="sb"&gt;

    [
      {
        "id": "gpu-12b-qat-mtp-6000-devops-agent",
        "object": "model",
        "owned_by": "vllm"
      }
    ]

&lt;/span&gt;  Health Status ( /health ):

  • Status: Healthy ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross Check The Deployed Model
&lt;/h4&gt;

&lt;p&gt;Once the model starts locally- the MCP tool allows for model verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Model health check PASSED.
  Model: gpu-12b-qat-mtp-6000-devops-agent
  Response: 'Yes, the model is working! How can I help you...'
  Latency: 3.51 seconds.
  _
  ✅ Model health check PASSED.

• Model: gpu-12b-qat-mtp-6000-devops-agent (running QAT + MTP Speculative Decoding on RTX 6000)
  • Latency: 3.51 seconds
  • Response: 'Yes, the model is working! How can I help you...'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; get_system_details

  Excellent! Everything is fully functional and the health checks are 100% green. Let's present the results.### 🖥️ System
  Status Dashboard: gpu-12b-qat-mtp-6000-devops-agent 
  Inference Stack Health:

  • vLLM Health: 🟢 Online (Healthy) ✅
  • Service URL: https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app
  • Model Health Check: PASSED (Latency: 1.62 seconds)
  • Active Model: gpu-12b-qat-mtp-6000-devops-agent 

  GCP Infrastructure Status:

  • Region: us-central1 
  • Cloud Run Active Revision: gpu-12b-qat-mtp-6000-devops-agent-00002-zcr 
  • Ready State: Ready ( True )
  • Scaling Configuration: Max: 1, Min: 1 (Active Instances: 1)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the Cloud Run deployment status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show the cloud run docker status

● Bash(VLLM_BASE_URL="https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app" GOOGL...) (ctrl+o to
expand)

  The status of the Cloud Run Docker container remains fully healthy and operational:

  ### 🖥️ Active Container Status Dashboard

  • vLLM Health: 🟢 Online (Healthy) ✅
  • Endpoint URL: gpu-12b-qat-mtp-6000-devops-agent https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.
  run.app
  • Active Revision: gpu-12b-qat-mtp-6000-devops-agent-00002-zcr 
  • Cloud Run Status: Ready ( True )
  • Scaling Limit: min=1 , max=1 (1 active instance)
  • Runtime Environment: vLLM nightly with QAT ( compressed-tensors ) and MTP speculative decoding enabled.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Check the Cloud Run Service
&lt;/h4&gt;

&lt;p&gt;Once Deployed — the model is visible as a standard Cloud Run Service:&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%2Fx4ingmuj42m7e5qqv26i.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%2Fx4ingmuj42m7e5qqv26i.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Review the Model
&lt;/h4&gt;

&lt;p&gt;Check the model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the model details?

● Bash(VLLM_BASE_URL="https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app" GOOGL...) (ctrl+o to
expand)

  Here are the model details retrieved directly from the active vLLM endpoint:

  ### 🧩 Active Model Configuration

  • Endpoint URL: https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app 
  • Model ID (served name): gpu-12b-qat-mtp-6000-devops-agent 
  • Object Type: model 
  • Owned By: vllm 
  • Health Status: Healthy ✅

  ### ⚙️ Under-the-Hood Serving Specs

  • Primary Weight Checkpoint: google/gemma-4-12B-it-qat-w4a16-ct (Quantization-Aware Training INT4 format)
  • Speculative Decoding (MTP): Assisted by google/gemma-4-12B-it-assistant (Multi-Token Prediction method with 3
  speculative tokens)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is online — you can send queries to the Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; query_gemma4_with_stats what is Gemma?
5.6k in | 500 out | 188 thinking
● Bash(VLLM_BASE_URL="https://gpu-12b-qat-mtp-6000-devops-agent-289270257791.us-central1.run.app" GOOGL...) (ctrl+o to
expand)

  Here is the query result and performance statistics generated by the active QAT + MTP RTX 6000 stack:

  ### 📊 Performance Stats

  • Model: gpu-12b-qat-mtp-6000-devops-agent 
  • Time to First Token (TTFT): 0.135s (extremely low latency)
  • Total Generation Time: 4.031s 
  • Tokens per Second: 70.33 tokens/s (high throughput generation)
  • Total Tokens (approx.): 274 
  ──────
  ### 💬 Model Response

  Gemma is a family of lightweight, state-of-the-art open models built by Google DeepMind.

  To understand what Gemma is, it helps to look at its relationship to Google’s other AI models and its specific
  characteristics:

  #### 1. The Relationship to Gemini

  Gemma is built using the same technology, architecture, and infrastructure as Google's Gemini models (the high-
  performance models that power Google's consumer-facing AI). However, while Gemini is a "closed" model (accessible only
  via API or Google products), Gemma is "open," meaning the weights are released for developers to download and run.

  #### 2. Key Characteristics

  • Open Weights: This means developers, researchers, and hobbyists can download the model, run it on their own hardware
  (like a personal laptop or a local server), and customize it.
  • Efficiency: Gemma models are designed to be "small" enough to run on consumer-grade hardware (like NVIDIA GPUs or
  even some laptops) while being "smart" enough to handle complex tasks.
  • Responsibility-Focused: Google trained Gemma with strict safety guidelines and "Responsible AI" techniques to
  minimize bias and prevent the generation of harmful content.

  #### 3. Common Model Sizes

  Gemma usually comes in different sizes to suit different needs:

  • Gemma 2B / 7B / 9B / 27B: These numbers represent billions of parameters (the "size" of the model's brain).
      • Smaller models (e.g., 2B, 9B) are designed for speed and running on local devices.
      • Larger models (e.g., 27B) are designed for more complex reasoning and higher-quality outputs.

  #### 4. What can you do with Gemma?

  Developers and researchers use Gemma for:

  • Text Generation: Writing stories, emails, or scripts.
  • Summarization: Condensing long documents into short summaries.
  • Coding Assistance: Helping write, debug, and explain programming code.
  • Fine-Tuning: This is the biggest use case. Because the weights are open, a company can take a base Gemma model and
  "train" it on their specific private data (e.g., a legal firm training it on their specific past cases).
  • Education &amp;amp; Research: Because it is accessible, it allows students and researchers to study how LLMs (Large Language
  Models) work internally.

  #### Summary Comparison: Gemini vs. Gemma

   Feature | Gemini | Gemma
  ---------------------------------------|---------------------------------------|---------------------------------------
   Access | Closed (API/Web) | Open Weights (Downloadable)
   Scale | Massive (Needs huge data centers) | Compact (Can run on a laptop)
   Use Case | Enterprise apps, Chatbots | Development, Research, Local apps
   Provider | Google | Google

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benchmark the Local Model
&lt;/h4&gt;

&lt;p&gt;The local model was benchmarked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I have completed the benchmark sweep and generated a full performance report.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The analysis is reviewed:&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="gt"&gt;&amp;gt; summarize benchmark_sweep_results.md&lt;/span&gt;

  Here is a summary of the performance trends from the benchmark sweep conducted on the Gemma 4 12B QAT speculative
  decoding stack ( --spec-tokens 3 --spec-method mtp ) running on the NVIDIA RTX PRO 6000 GPU Cloud Run instance:

  ### 1. Latency Characteristics &amp;amp; scaling

  • Ultra-low Concurrency (1–2 users): Latency was incredibly low, ranging from 0.13s to 0.30s for context sizes up
  to 4096 tokens. Even at the maximum 16K context, average latency was only 1.17s for 1 user.
  • The "Sweet Spot" (8–128 users): Thanks to speculative decoding (MTP) and FP8 KV caching, concurrency scaled
  gracefully. Up to 128 users, average latency remained under 1.0s for context lengths up to 256 tokens.
  • High Concurrency (1024–2048 users): Latencies scaled up linearly under heavy queuing, averaging 6.5s to 10.0s at
  1024 users and up to 23s to 34s at 2048 users.

  ### 2. Throughput Efficiency

  • Peak Output: The stack reached its highest throughput of 209.4 Requests/sec at a context size of 4 tokens under
  128 concurrent users.
  • Throughput Sweet Spot: For medium context sizes (e.g., 128 to 512 tokens), throughput peaked at 55 to 80 
  Requests/sec, typically around 128 or 256 concurrent users.
  • High-Context Capacity: Even at a massive 16K context window, the RTX 6000 maintained a solid peak throughput of  
&lt;span class="p"&gt;  34.&lt;/span&gt;2 Requests/sec at 256 concurrent users before declining.

  ### 3. Queue Stability &amp;amp; Error Limits

  • 100% Success Rates: The serving stack proved highly stable, maintaining a 100% success rate across almost all tests
  up to 512 users.
  • Failure Boundaries: Success rates began dropping at extreme load bounds:
      • For context windows of 128 to 512 tokens, success rates fell to 37% – 58% under the maximum load of 2048 
      concurrent users.
      • At a 16K context size, timeouts or resource boundaries began appearing starting at 16 users ( 93.8% success
      rate), dropping to 35.8% at 2048 users.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is visualized:&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%2Fl2i821m9g7ej8rfpohfd.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%2Fl2i821m9g7ej8rfpohfd.png" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did QAT and 3 deep speculative decoding help?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
  Yes, the QAT model combined with 3-token Multi-Token Prediction (MTP) speculative decoding provided clear improvements,
  particularly at shorter context sizes and low-to-medium concurrencies. However, it also reveals a trade-off at higher
  context lengths under heavy load.

  Here is a direct comparison between the Standard FP8 Model and the new QAT + MTP Stack at key test intervals:

  ### 1. Standard Context Window ( 128 tokens) — Clear Wins

  For typical short-context workloads, the QAT + MTP setup significantly outperformed standard serving:

  • 8 Users: Latency decreased by 11.2% ( 0.255s → 0.227s ), and request throughput increased by 10.4% ( 29.6 → 32.7 
  req/s).
  • 32 Users: Latency decreased by 17.5% ( 0.480s → 0.396s ), and request throughput increased by 12.3% ( 52.9 → 59.4
  req/s).
  • 128+ Users: The performance was neck-and-neck, with latency matching standard serving within ±2%.

  ### 2. Large Context Window ( 1024 tokens) — The Trade-Off

  As the context size grows, speculative decoding behaves differently depending on the load:

  • Under Light Load (8 Users): The QAT + MTP configuration still wins, showing a 5.8% latency reduction ( 0.297s → 0.
  280s ) and a 14.9% throughput increase ( 24.2 → 27.8 req/s).
  • Under Heavy Load (32+ Users): The QAT + MTP configuration experienced performance degradation. For example, at 128
  users, latency was 41.4% higher ( 0.992s → 1.402s ) and throughput was 32.3% lower.

  ### Why does this happen?

  1. Low-Concurrency Acceleration: Speculative decoding (MTP) accelerates token generation by predicting multiple tokens
  in a single forward pass. When the GPU has free compute resources (low concurrency), this translates directly into
  faster output and lower latency.
  2. High-Concurrency Overhead: Speculative verification requires extra memory bandwidth and KV cache management. When
  concurrency is high and context is long, the GPU becomes memory-bandwidth saturated. The overhead of verifying
  speculative tokens ends up slowing down total throughput compared to standard generation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How does this compare to other Hardware Options?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; compare to other hardware options at 12B

11.1k in | 2.1k out | 603 thinking
● Bash(python3 /home/xbill/.gemini/antigravity-cli/brain/7c3e47cc-13be-46df-96b5-7549adf8fa88/scratch/c...) (ctrl+o to
expand)

  Here is a hardware comparison of performance for the Gemma 4 12B model running across three cloud accelerator
  configurations:

  1. NVIDIA RTX PRO 6000 GPU (with QAT + 3-token speculative decoding, Blackwell 96GB) — Our current run
  2. NVIDIA L4 GPU (with QAT INT4, Ada Lovelace 24GB)
  3. Google TPU v6e-1 (Single TPU v6e chip)
  ──────
  ### 1. Short Context Workloads ( 128 tokens)

  Under standard context sizes, the RTX 6000 excels at low-to-medium concurrencies, while the TPU v6e dominates at scale:

  • 8 Users (Low Load):
      • 🥇 RTX 6000: 0.227s (32.7 req/s) — Fastest latency and throughput
      • 🥈 L4 GPU: 0.329s (18.9 req/s)
      • 🥉 TPU v6e-1: 0.331s (22.9 req/s)
  • 32 Users (Medium Load):
      • 🥇 RTX 6000: 0.396s (59.4 req/s) — Lowest latency
      • 🥈 TPU v6e-1: 0.456s (68.6 req/s) — Highest throughput
      • 🥉 L4 GPU: 0.827s (22.6 req/s)
  • 128 Users (High Load):
      • 🥇 TPU v6e-1: 0.434s (71.2 req/s) — Maintains near-constant flat latency
      • 🥈 RTX 6000: 0.958s (76.6 req/s)
      • 🥉 L4 GPU: 2.726s (24.4 req/s)

  ──────
  ### 2. Large Context Workloads ( 1024 tokens)

  As context window demands increase, hardware memory bandwidth differences become more pronounced:

  • 8 Users (Low Load):
      • 🥇 RTX 6000: 0.280s (27.8 req/s)
      • 🥈 TPU v6e-1: 0.348s (22.7 req/s)
      • 🥉 L4 GPU: 0.361s (17.2 req/s)
  • 128 Users (High Load):
      • 🥇 TPU v6e-1: 0.455s (65.5 req/s) — Outstanding high-load concurrency scaling
      • 🥈 RTX 6000: 1.402s (51.2 req/s)
      • 🥉 L4 GPU: 3.062s (22.1 req/s)

  ──────
  ### Key Takeaways

  1. Low-Latency/Low-Load Winner: The RTX PRO 6000 (with QAT + Speculative Decoding) is the fastest accelerator under
  light-to-moderate loads, processing requests around 30% faster than the other options.
  2. Concurrency/High-Load Winner: The TPU v6e-1 has exceptional latency-scaling characteristics. As load grows from 8
  users to 128 users, the TPU's average latency barely changes ( 0.348s → 0.455s ), whereas the GPUs scale latency up
  significantly.
  3. Budget/Scale Winner: The L4 GPU represents the entry-level tier. While it lags behind the high-memory-bandwidth
  alternatives, it provides stable performance at lower hardware costs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about other Gemma Model sizes?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; how does this compare to the 2B,4B,26B,31B across hardware deployments

&amp;gt; how does this compare to the 2B,4B,26B,31B across hardware deployments

25.2k in | 4.0k out | 1.1k thinking
● Bash(python3 /home/xbill/.gemini/antigravity-cli/brain/7c3e47cc-13be-46df-96b5-7549adf8fa88/scratch/c...) (ctrl+o to
expand)

  Here is a cross-hardware deployment comparison of Gemma 4 performance across different model sizes ( 2B , 4B , 12B , 
  26B , and 31B ), captured under a standard moderate load of 8 concurrent users at a short context size ( 8 tokens):

   Model Size | Deployment Hardware | Latency | Request Throughput | Notes / Quantization
  ---------------|---------------------|--------------|--------------------|---------------------------------------------
   2B | Mac (M-series) | 0.813s | 1.9 req/s | Local inference (Ollama/Llama.cpp)
   12B | RTX 6000 GPU | 0.151s | 50.4 req/s | QAT (INT4) + MTP (3-spec)
   12B | L4 GPU | 0.256s | 25.1 req/s | QAT (INT4)
   12B | TPU v6e-1 | 0.518s | 16.6 req/s | Native TPU runtime
   26B | RTX 6000 GPU | 1.020s | 7.8 req/s | FP8 Quantized
   26B | L4 GPU | 0.159s | 39.9 req/s | QAT (INT4)
   26B | TPU v6e-1 | 2.723s | 2.5 req/s | Native TPU runtime (longer completion test)
   31B | TPU v6e-1 | 0.309s | 25.2 req/s | Native TPU runtime
   31B | L4 GPU | 0.256s | 25.1 req/s | QAT (INT4)
   31B | RTX 6000 GPU | 3.445s | 2.3 req/s | Standard FP8
  ──────
  ### Key Architectural &amp;amp; Hardware Insights

  1. Quantization Impact (QAT INT4):
      • QAT (Quantization-Aware Training) to INT4 enables large models to run with minimal footprint. This is why the
      26B QAT and 31B QAT on the standard L4 GPU achieve latency profiles ( ~0.159s and ~0.256s ) that are comparable
      to or faster than unquantized smaller models.
      • On unquantized models (like the standard 26B and 31B on the RTX 6000), standard FP8 serving exhibits higher
      latencies ( 1.02s and 3.44s ) due to the significantly larger memory bandwidth requirements during weights
      loading.
  2. Specular/Assistant Decoding Boost (MTP):
      • Our benchmarked 12B QAT + MTP configuration on the RTX 6000 achieves the absolute best performance at this scale
      ( 0.151s latency / 50.4 req/s throughput), demonstrating the power of combining model compression (QAT) with
      speculative drafting (3-token MTP).
  3. TPU v6e-1 Concurrency Efficiency:
      • For the 31B model, the TPU v6e-1 matches the L4 QAT performance ( 0.309s vs 0.256s ) without needing QAT
      quantization. TPUs have high native matrix multiplication hardware density and memory bandwidth, making them highly
      efficient at running larger model parameters natively without degradation.

────────────────────────────────────────────────────────────
&amp;gt; repeat with a 256 context

4.6k in | 1.7k out | 126 thinking
● Bash(python3 /home/xbill/.gemini/antigravity-cli/brain/7c3e47cc-13be-46df-96b5-7549adf8fa88/scratch/c...) (ctrl+o to
expand)

  Here is the performance comparison across deployments at a context window of 256 tokens (under the same moderate load
  of 8 concurrent users):

   Model Size | Deployment Hardware | Latency | Request Throughput | Notes / Quantization
  --------------|---------------------|--------------|--------------------|----------------------------------------------
   2B | Mac (M-series) | 0.813s | 1.9 req/s | Local fallback (No context sweep data)
   12B | RTX 6000 GPU | 0.255s | 30.5 req/s | QAT (INT4) + MTP (3-spec)
   12B | L4 GPU | 0.349s | 18.1 req/s | QAT (INT4)
   12B | TPU v6e-1 | 0.504s | 17.2 req/s | Native TPU runtime
   26B | L4 GPU | 0.294s | 21.7 req/s | QAT (INT4) — Outstanding scaling
   26B | TPU v6e-1 | 2.723s | 2.5 req/s | Native TPU runtime (longer completion sweep)
   31B | L4 GPU | 0.349s | 18.1 req/s | QAT (INT4)

  (Note: Data points marked with "No context sweep data" represent standalone benchmark falls where a context sweep was
  not configured in the test harness.)
  ──────
  ### Key Observations at 256 Context

  1. Speculative Decoding Efficiency:
      • The 12B QAT + MTP configuration on the RTX 6000 remains the clear class leader, achieving 0.255s average
      latency and 30.5 req/s .
  2. QAT INT4 on L4 Scalability:
      • The 26B QAT model on a single cost-effective L4 GPU performs incredibly well, processing requests at 0.294s 
      latency with 21.7 req/s . It is only slightly behind the 12B model, indicating that QAT compression minimizes the
      latency penalty typically associated with doubling model size.
      • Similarly, the 31B QAT on L4 matches the 12B QAT on L4 exactly at 0.349s latency and 18.1 req/s , highlighting
      the computational efficiency of the QAT path.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cost Breakdowns
&lt;/h4&gt;

&lt;p&gt;Model serving costs were compared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; do a cost analysis

  Here is a cost-efficiency analysis comparing these hardware deployment options.
  To make the comparison uniform, we analyze the cost to serve 1 Million Requests at a 256-token context size under a
  moderate load (8 concurrent users).

  ### 1. Estimated Hourly Cloud Rates (Active Runtime)

  • L4 GPU (Cloud Run G2 standard): ~$1.40/hr (Includes 1 L4 GPU + 4 vCPU + 16GB RAM)
  • TPU v6e-1 (GCP On-Demand): ~$1.20/hr (Single-chip TPU node)
  • RTX PRO 6000 GPU (Cloud Run Premium): ~$2.50/hr (96GB premium GPU allocation + CPU + RAM)
  • Mac M-Series (Local): $0.00/hr (Self-hosted local workstation)
  ──────
  ### 2. Serving Cost-Efficiency (Cost per 1 Million Requests)

  Calculated using:

                       ⎛Hourly Hardware Cost⎞
    Cost per Million = ⎜────────────────────⎟ × 1,000,000
                       ⎝ Requests per Hour ⎠

   Model &amp;amp; Hardware Option | Throughput (Req/… | Requests / H… | Hourly Cost | Cost per 1M R… | Efficiency Rank
  ---------------------------|-------------------|---------------|-------------|----------------|------------------------
   12B on TPU v6e-1 (Native) | 17.2 req/s | 61,920 | $1.20/hr | $19.38 | 🥈 #2 (Best Cloud 12B)
   12B on L4 GPU (QAT INT4) | 18.1 req/s | 65,160 | $1.40/hr | $21.49 | 🥉 #3
   12B on RTX 6000 (QAT+MTP) | 30.5 req/s | 109,800 | $2.50/hr | $22.77 | #4 (Lowest Latency)
   26B on L4 GPU (QAT INT4) | 21.7 req/s | 78,120 | $1.40/hr | $17.92 | 🥇 #1 (Best Overall)
   31B on L4 GPU (QAT INT4) | 18.1 req/s | 65,160 | $1.40/hr | $21.49 | 🥉 #3 (Tie)
   2B on Mac (M-Series) | 1.9 req/s | 6,840 | $0.00/hr | $0.00 | 👑 Free (Workstation)
  ──────
  ### 🔍 Key Cost Observations

  1. The 26B QAT L4 Sweet Spot:
      • Serving the 26B QAT model on a single L4 GPU is the most cost-efficient cloud option, costing only $17.92 per
      million requests. This configuration strikes an optimal balance between low hourly GPU costs and elevated QAT
      throughput.
  2. Latency vs. Cost (RTX 6000):
      • Serving 12B QAT + MTP on the RTX 6000 gives the lowest latency ( 0.255s ), but it costs slightly more per request
      ( $22.77 per million) due to the higher base hourly rate of the premium 96GB GPU. Choose this when raw speed/low
      TTFT is the primary metric.
  3. TPU v6e-1 for Heavy Scaling:
      • TPU v6e-1 represents excellent native value ( $19.38 per million for 12B). Because TPU nodes can handle
      concurrency scaling without a corresponding rise in latency, the cost-efficiency gains increase significantly under
      higher concurrencies (128+ users).
  4. Cloud Run Auto-scaling Benefit:
      • Using Cloud Run allows the service to scale down to 0 when idle. For dev/test environments or variable
      production patterns, scaling to 0 dramatically reduces the total monthly bill compared to running dedicated 24/7
      VMs.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what is the final offer? Survey says …&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ### 📋 Executive Summary Highlights:

  1. Performance Champion: The 12B QAT (INT4) + 3-token speculative decoding (MTP) setup on the RTX 6000 GPU achieves the
  lowest average latency under typical loads ( 0.255s at a 256 token context).
  2. Operational Cost Winner: The 26B QAT on L4 GPU represents the most economical cloud configuration, costing only  
  $17.92 per 1 Million Requests (at a 256 context size).
  3. Concurrency Powerhouse: The TPU v6e-1 scales concurrency with almost zero latency impact (latency remains flat at
  ~0.45s scaling all the way from 8 to 128 concurrent users).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The strategy for using MCP for Gemma 4 GPU deployment with Antigravity CLI was validated with a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;A minimal stdio transport MCP Server was started from Python source code and validated with Antigravity CLI running as a MCP client in the same local environment. This Python server provided all of the management tools to deploy and troubleshoot Cloud Run Model deployments.&lt;/p&gt;

</description>
      <category>speculativedecoding</category>
      <category>antigravitycli</category>
      <category>qat</category>
      <category>blackwell6000</category>
    </item>
    <item>
      <title>Serverless Gemma 12B with NVIDIA A100 on Azure Container Apps</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Mon, 22 Jun 2026 17:13:03 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/serverless-gemma-12b-with-nvidia-a100-on-azure-container-apps-1ff4</link>
      <guid>https://dev.clauneck.workers.dev/gde/serverless-gemma-12b-with-nvidia-a100-on-azure-container-apps-1ff4</guid>
      <description>&lt;p&gt;This article provides a step by step debugging guide for deploying Gemma 4 to Azure Container Apps. A suite of Python MCP tools is built to simplify management of the vLLM hosted Gemma 4 deployment with Antigravity CLI.&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.amazonaws.com%2Fuploads%2Farticles%2F3360w3vqaf31m7sj2e2u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3360w3vqaf31m7sj2e2u.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is this project trying to Do?
&lt;/h4&gt;

&lt;p&gt;This project is a DevOps/SRE assistant that uses a Gemma 4 model hosted on Azure Container APps. It provides tools to provision the Docker container and deploy the model, as well as for observability and performance testing.&lt;/p&gt;

&lt;p&gt;This project is similar to a previous project that targeted GPU hosted Gemma4 instances on GCP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/gemma-sre-self-hosted-vllm-infrastructure-agent-afda000c0943" rel="noopener noreferrer"&gt;Gemma-SRE: Self-Hosted vLLM Infrastructure Agent&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Installation Instructions
&lt;/h4&gt;

&lt;p&gt;The detailed installation instructions for Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure CLI
&lt;/h4&gt;

&lt;p&gt;The Azure Command-Line Interface (CLI) is a cross-platform tool used to connect to Azure and execute administrative commands on Azure resources. It allows you to manage services like virtual machines, databases, and networking through a terminal using interactive prompts or scripts. [&lt;a href="https://learn.microsoft.com/en-us/cli/azure/what-is-azure-cli?view=azure-cli-latest" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/cli/azure/?view=azure-cli-latest" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://sumble.com/tech/azure-cli" rel="noopener noreferrer"&gt;3&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;More details are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/overview?tabs=linux" rel="noopener noreferrer"&gt;What is the Azure Developer CLI?&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Container Apps
&lt;/h4&gt;

&lt;p&gt;Azure Container Apps is a fully managed, serverless Kubernetes-based application platform designed for building and deploying modern, containerized apps without managing complex infrastructure. It enables scaling from zero to high demand, supports microservices, and handles event-driven processing with built-in HTTPS and observability.&lt;/p&gt;

&lt;p&gt;Full details are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/products/container-apps" rel="noopener noreferrer"&gt;https://azure.microsoft.com/en-us/products/container-apps&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.amazonaws.com%2Fuploads%2Farticles%2Fxzsiipedq5e6auvt0f5n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzsiipedq5e6auvt0f5n.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Nvidia A100 on ACA
&lt;/h4&gt;

&lt;p&gt;ACA offers two sizes of GPU. The T4 is a cost effective solution but has limited GPU memory. The A100 provides more resources for performance sensitive deployments but comes with a higher cost and a much larger computer VM to manage the deployment. Details are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/container-apps/gpu-types" rel="noopener noreferrer"&gt;Comparing GPU types in Azure Container Apps&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Where do I start?
&lt;/h4&gt;

&lt;p&gt;The strategy for starting MCP development for model management is a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;First, the basic development environment is setup with the required system variables, and a working Antigravity CLI configuration.&lt;/p&gt;

&lt;p&gt;Then, a minimal Python MCP Server is built with stdio transport. This server is validated with Antigravity CLI in the local environment.&lt;/p&gt;

&lt;p&gt;This setup validates the connection from Antigravity CLI to the local server via MCP. The MCP client (Antigravity CLI) and the Python MCP server both run in the same local environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup the Basic Environment
&lt;/h4&gt;

&lt;p&gt;At this point you should have a working Python environment and a working Antigravity CLI installation. The next step is to clone the GitHub samples repository with support scripts:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/xbill9/gemma4-tips-azure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;init.sh&lt;/strong&gt; from the cloned directory.&lt;/p&gt;

&lt;p&gt;The script will attempt to determine your shell environment and set the correct variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpu-12B-qat-aca-a100-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session times out or you need to re-authenticate- you can run the &lt;strong&gt;set_env.sh&lt;/strong&gt; script to reset your environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpu-12B-qat-aca-a100-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Management Tool with MCP Stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the standard MCP libraries provide is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;The connection over stdio will look similar to this:&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;# Initialize FastMCP server
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Self-Hosted vLLM DevOps Agent&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;h4&gt;
  
  
  Running the Python Code
&lt;/h4&gt;

&lt;p&gt;First- switch the directory with the Python version of the MCP sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/gemma4-tips-azure/gpu-12B-qat-aca-a100-devops-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the release version on the local system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;xbill@penguin:~/gemma4-tips-azure/gpu-12B-qat-aca-a100-devops-agent$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;pip install -r requirements.txt
Requirement already satisfied: mcp in /home/xbill/.pyenv/versions/3.13.13/lib/python3.13/site-packages (from -r requirements.txt (line 1)) (1.27.2)
Requirement already satisfied: fastmcp in /home/xbill/.pyenv/versions/3.13.13/lib/python3.13/site-packages (from -r requirements.txt (line 2)) (2.11.3)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project can also be linted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;mxbill@penguin:~/gemma4-tips-azuregpu-12B-qat-aca-a100-devops-agent$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make lint
&lt;span class="go"&gt;ruff check .
All checks passed!
ruff format --check .
7 files already formatted
mypy .
Success: no issues found in 7 source files

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MCP stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the MCP protocol provides is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;In this project Antigravity CLI is used as the MCP client to interact with the Python MCP server code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI mcp_config.json
&lt;/h4&gt;

&lt;p&gt;A sample MCP server file is provided in the .agents directory:&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="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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"gpu-devops-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"/home/xbill/gemma4-tips-azure/gpu-12B-qat-aca-a100-devops-agent/server.py"&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;"env"&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;"AZURE_LOCATION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eastus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"MODEL_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google/gemma-4-12B-it-qat-w4a16-ct"&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;h4&gt;
  
  
  Validation with Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;The final connection test uses Antigravity CLI as a MCP client with the Python code providing the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
&amp;gt; ✓ google-dev-knowledge Tools: search_documents, answer_query, get_documents
   ✓ gpu-devops-agent Tools: save_hf_token, get_vllm_endpoint, list_vertex_models, list_bucket_models,
                       analyze_cloud_logging, +25 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting Started with Gemma 4 on GPU
&lt;/h4&gt;

&lt;p&gt;The Official vLLM repo also has Gemma4 specific information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm/releases" rel="noopener noreferrer"&gt;Releases · vllm-project/vllm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Gemma 12B model was just released:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/introducing-gemma-4-12b/" rel="noopener noreferrer"&gt;Introducing Gemma 4 12B: a unified, encoder-free multimodal model&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What about the QAT Models?
&lt;/h4&gt;

&lt;p&gt;A deep dive into deploying the 12B QAT model is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xbill999.medium.com/12b-gemma-4-qat-deployment-with-nvidia-l4-cloud-run-mcp-and-antigravity-cli-944d603b4ab5" rel="noopener noreferrer"&gt;12B Gemma 4 QAT Deployment with NVIDIA L4, Cloud Run, MCP, and Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Lets Deploy this!
&lt;/h4&gt;

&lt;p&gt;The model was deployed to Consumption-GPU-NC24-A100 backed with a NVIDIA GPU:&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="gt"&gt;&amp;gt; status_azure_vm&lt;/span&gt;&lt;span class="sb"&gt;

    ### Azure Container App Status for service 'gemma4-vllm-gpu':

    - **Container App Name** : `gemma4-vllm-gpu`
      - **Provisioning State** : `Succeeded`
      - **Running Status** : `Running`
      - **FQDN** : `https://gemma4-vllm-gpu.happyfield-ebede3ad.eastus.azurecontainerapps.io`
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check the Docker Container:&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="gt"&gt;&amp;gt; check_vllm&lt;/span&gt;

● gpu-devops-agent/check_vllm(Call check_vllm) (ctrl+o to expand)

  The model endpoint is fully up and running healthily! Here is the status returned from check_vllm :

  ### 🖥️ Azure Container App: gemma4-vllm-gpu 

  • Provisioning State: Succeeded 
  • Running Status: Running 
  • FQDN: https://gemma4-vllm-gpu.happyfield-ebede3ad.eastus.azurecontainerapps.io 
  • vLLM API Health Check ( /health ): Healthy ✅ 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross Check The Deployed Model
&lt;/h4&gt;

&lt;p&gt;Once the model starts locally- the MCP tool allows for model verification:&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="gt"&gt;&amp;gt; verify_model_health&lt;/span&gt;
&lt;span class="p"&gt;
132.&lt;/span&gt;7k in | 94 out | 30 thinking
● gpu-devops-agent/verify_model_health(Call verify_model_health) (ctrl+o to expand)

  The deep health check passed successfully:

  • Target Model: google/gemma-4-12B-it-qat-w4a16-ct 
  • Health Check Status: PASSED ✅ 
  • Response: "Yes, I am working! How can I help you today?..."
  • Response Latency: 1.56 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and model details:&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="gt"&gt;&amp;gt; &amp;gt; check model details&lt;/span&gt;
&lt;span class="p"&gt;
3.&lt;/span&gt;2k in | 93 out | 29 thinking
● gpu-devops-agent/get_model_details(Call get_model_details) (ctrl+o to expand)

  Here are the detailed model and engine specifications retrieved from the running service:

  ### 🧩 Model Details (gemma4-vllm-gpu https://gemma4-vllm-gpu.happyfield-ebede3ad.eastus.azurecontainerapps.io)

  Model Information ( /v1/models ):&lt;span class="sb"&gt;

    [
      {
        "id": "google/gemma-4-12B-it-qat-w4a16-ct",
        "object": "model",
        "owned_by": "vllm"
      }
    ]

&lt;/span&gt;  Health Status ( /health ):

  • Status: Healthy ✅ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and status:&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="gt"&gt;&amp;gt; get_status&lt;/span&gt;
&lt;span class="p"&gt;
4.&lt;/span&gt;2k in | 145 out | 66 thinking
● gpu-devops-agent/get_system_status(Call get_system_status) (ctrl+o to expand)

  The system status details are as follows:

  ### 🌀 GPU vLLM System Status

  • vLLM Health: 🟢 Online (https://gemma4-vllm-gpu.happyfield-ebede3ad.eastus.azurecontainerapps.io)
  • Hosting Status: 🟢 ACA Succeeded ( gemma4-vllm-gpu )

  👉 Next Step: Use the query_gemma4 tool to interact directly with the deployed model.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  The prometheus metrics were successfully fetched from the active vLLM service. Here is a summary of the key performance
  and telemetry metrics:

  #### 📊 Telemetry &amp;amp; Resource Utilization

  • Virtual Memory Size: ~14.42 GB ( process_virtual_memory_bytes )
  • Resident Memory Size: ~2.45 GB ( process_resident_memory_bytes )
  • Total CPU Time Spent: 53.11s ( process_cpu_seconds_total )
  • Open File Descriptors: 49 ( process_open_fds )

  #### 🌀 vLLM Serving &amp;amp; Queue Telemetry

  • Running Requests: 0 ( vllm:num_requests_running )
  • Waiting Requests: 0 ( vllm:num_requests_waiting )
  • KV-Cache Usage: 0.0% ( vllm:kv_cache_usage_perc )
  • Prefix Cache Queries (Tokens): 20.0 ( vllm:prefix_cache_queries_total )
  • Engine Sleep State: Awake ( vllm:engine_sleep_state{sleep_state="awake"} = 1.0 )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Check the Azure Instance
&lt;/h4&gt;

&lt;p&gt;Once Deployed — the model is visible on the Azure Console:&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%2Fjjpiittovxijwj1ja9rq.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%2Fjjpiittovxijwj1ja9rq.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Review the Model
&lt;/h4&gt;

&lt;p&gt;Check the model details:&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="gt"&gt;&amp;gt; what are the model details&lt;/span&gt;
&lt;span class="p"&gt;
130.&lt;/span&gt;9k in | 60 out | 0 thinking
● gpu-devops-agent/get_model_details(Call get_model_details) (ctrl+o to expand)

  The details of the active running model are:

  ### 🧩 Deployed Model Info

  • Model ID: google/gemma-4-12B-it-qat-w4a16-ct 
  • Object Type: model 
  • Owned/Served By: vllm 
  • Endpoint URL: https://gemma4-vllm-gpu.happyfield-ebede3ad.eastus.azurecontainerapps.io/v1/models
  • Model Health Status: Healthy ✅

──────────────────────────────────────────────────
&lt;span class="gt"&gt;
&amp;gt; what are the A100 hardware details? Memory, VM, etc&lt;/span&gt;

  The active Azure Container Apps (ACA) GPU workload profile Consumption-GPU-NC24-A100 maps to the following Azure
  hardware specifications (equivalent to the Standard_NC24ads_A100_v4 VM size):

  ### 🛡️ Hardware &amp;amp; VM Specifications

  • GPU: 1x NVIDIA A100 PCIe (with 80 GB HBM2e High-Bandwidth VRAM)
  • vCPUs: 24 virtual CPUs
  • System Memory (RAM): 220 GiB 
  • Local Temp Storage: 360 GiB (NVMe SSD)
  • Maximum Network Bandwidth: Up to 20 Gbps 

  #### 💡 Deployment Advantages

  Deploying the google/gemma-4-12B-it-qat-w4a16-ct (Quantization-Aware Training w4a16 model) on this instance allows
  100% request success rate up to 512 concurrent users because the model weights only occupy a fraction of the 80 GB
  VRAM, leaving the vast majority of memory free for serving high-concurrency KV-caches.&lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gt"&gt;&amp;gt; query_gemma4_with_stats what is Gemma?&lt;/span&gt;
&lt;span class="p"&gt;
142.&lt;/span&gt;0k in | 114 out | 33 thinking
● gpu-devops-agent/query_gemma4_with_stats(Call query_gemma4_with_stats) (ctrl+o to expand)

  Here is the response and performance telemetry from the deployed Gemma 4 model:

  ### 📊 Performance Stats

  • Model: google/gemma-4-12B-it-qat-w4a16-ct 
  • Time to First Token (TTFT): 0.110s 
  • Total Generation Time: 3.280s 
  • Inference Speed: 98.73 tokens/s 
  • Total Tokens (approx.): 313 

  ### 💬 Model Response

  Gemma is a family of lightweight, state-of-the-art open models developed by Google DeepMind.

  Here are the key characteristics of the Gemma family:

  • Open Weights: Gemma is designed as an "open weights" model, meaning that while the underlying technology is
  proprietary, the model weights are shared with the developer community for broad use, research, and customization.
  • Built from Gemini Technology: Gemma models are built using the same research, infrastructure, and technology used to
  create Google's Gemini models (like Gemini 1.5 Pro and Gemini Ultra).
  • Scale and Efficiency: They are designed to be "lightweight," meaning they are smaller than the massive Gemini models.
  This makes them efficient enough to run on personal hardware (like laptops or workstations) and local cloud
  infrastructure, while still maintaining high performance for their size.
  • Modalities: Different versions of Gemma support different modalities. While many are text-focused, newer versions
  (like Gemma 2 and parts of the Gemma 4 family) are capable of processing text and images, and some versions (like the
  2B and 4B models) can also process audio input.
  • Responsible AI: They are released with a focus on safety and responsibility, incorporating Google’s AI Principles and
  including safety filters and fine-tuning to minimize harmful outputs.

  In short, Gemma is Google's contribution to the open model ecosystem, providing developers and researchers with
  powerful, efficient tools for building AI applications.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benchmark the Local Model
&lt;/h4&gt;

&lt;p&gt;The local model was benchmarked. Summarize the results:&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="gt"&gt;
&amp;gt; summarize benchmark_report.md&lt;/span&gt;

  Here is a summary of the 2D grid benchmark sweep findings for the Gemma 4 12B QAT model ( google/gemma-4-12B-it-qat-
  w4a16-ct ) served on an NVIDIA A100 GPU in Azure Container Apps:

  ### 🚀 Key Takeaways
&lt;span class="p"&gt;
  1.&lt;/span&gt; Outstanding Load Stability: The INT4 QAT model maintains a 99.6% to 100% success rate across all context windows
  (from 4 tokens to 16,384 tokens) and concurrencies up to 1024 concurrent users. This is a major improvement over the
  standard bfloat16 model, which frequently struggles at higher concurrency due to KV cache memory pressures.
&lt;span class="p"&gt;  2.&lt;/span&gt; Throughput Ceiling: Maximum throughput peaks at ~69.2 requests/second (achieved at 256 concurrent users with a tiny
  4-token context window). As the context size grows, the throughput decreases due to processing time, stabilizing at
  ~26.8 requests/second for the 16K context window at high concurrencies.
&lt;span class="p"&gt;  3.&lt;/span&gt; Linear Latency Scaling: Latency scales predictably. Under low loads (1–8 users), responses return in 0.11s – 0.5s.
  At peak load (1024 users), latency reaches ~9.53s for small contexts and ~19.80s for the maximum 16K context window.

  ### 🕒 Performance Profiles

  • Small Context (4 – 256 tokens): Optimal concurrency is around 128–256 users (throughput reaches ~69 req/s, average
  latency is ~1.06s – 2.01s).
  • Medium Context (512 – 2048 tokens): Throughput peaks at ~47–50 req/s for concurrent users ≥128. Latency scales from
  ~0.2s (at low concurrency) to ~11s (at 1024 concurrent users).
  • Large Context (4096 – 16384 tokens): Prefill and generation latency dominates at low concurrency (2.24s for a single
  user at 16K context) and peaks at ~19.80s under 1024 concurrent users. Throughput maxes out at ~26.8 req/s.

  ### Summary of Graph Trends

  • Latency vs. Concurrent Users (Top Plot):
      • Shows flat, sub-second latency for all context windows up to 64 concurrent users.
      • Shows a linear scale-up in latency starting from 128 concurrent users due to queue serialization.
      • Shows the clear separation of the 16K context line (purple), which peaks at ~19.8s latency under 1024 concurrent
      users.
  • Throughput vs. Concurrent Users (Bottom Plot):
      • Highlights how throughput ramps up quickly as concurrency increases, peaking around 128–256 concurrent users.
      • Shows throughput leveling off as the GPU reaches saturation.
      • Demonstrates that the throughput ceiling drops from ~69 req/s for small prompts (blue line) to ~26.8 req/s for
      16K prompts (purple line).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is visualized:&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%2F9it57ev0ha6mvuhaljx4.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%2F9it57ev0ha6mvuhaljx4.png" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  GPU Memory Allocation of QAT 12B Models
&lt;/h4&gt;

&lt;p&gt;The GPU provides 80 GB of GPU Memory:&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="gt"&gt;&amp;gt; what is the memory breakdown on the GPU?&lt;/span&gt;
  Based on the startup logs of the vLLM engine running on the NVIDIA A100 (80GB) GPU, here is the detailed breakdown of
  the GPU memory (VRAM):

  ### 📊 GPU Memory (VRAM) Breakdown

   Component | VRAM Allocation | % of Total VRAM | Purpose
  --------------------|-----------------|-----------------|--------------------------------------------------------------
   Model Weights | 8.29 GiB | 10.4% | Model parameters (INT4 QAT compressed weights)
   KV Cache | 66.26 GiB | 82.8% | Dynamic KV cache block allocation (1,050,170 total tokens
                      | | | capacity)
   CUDA Graphs | 0.06 GiB | 0.1% | Compiled execution graphs for rapid inference operations
   Reserved Overhead | 1.39 GiB | 1.7% | Working memory for intermediate tensors and pytorch
                      | | | activations
   Unallocated / Free | 4.00 GiB | 5.0% | Unused VRAM headroom (since --gpu-memory-utilization is
                      | | | capped at 0.95 )
   Total | 80.00 GiB | 100.0% | NVIDIA A100 80GB VRAM Capacity

  ### 💡 Key SRE Insights

  • The QAT Advantage: Loading the model in native 16-bit precision would require ~24 GiB of VRAM just for weights. Using
  the INT4 Quantization-Aware Training (QAT) format compresses this to only 8.29 GiB, leaving 66.26 GiB entirely free for
  the KV cache.
  • Large KV Cache Capacity: The un-allocated space allows the KV cache to hold up to 1,050,170 tokens. This massive
  capacity is what allows the server to handle 1024 concurrent users at 16K context window with a 100% success rate
  without OOM crashes.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What are the performance characteristics of the Model?&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="gt"&gt;&amp;gt; what are the performance characteristics of the deployment?&lt;/span&gt;

  Based on the benchmark sweep and the vLLM serving configuration on the NVIDIA A100 GPU (80GB), here are the core
  performance characteristics of the deployment:

  ### 1. Throughput &amp;amp; Scaling Characteristics

  • Peak Throughput Ceiling: Saturation occurs around 69.2 requests/second for small prompt sizes (≤32 tokens) at ≥256
  concurrent users.
  • Large Context Throughput: Under high context load (e.g., 16,384 tokens), throughput caps at ~26.8 requests/second
  starting from 256 concurrent users and remains perfectly flat up to 1024 concurrent users.
  • Concurrency Efficiency: Due to the compressed weights freeing up 82.8% of the VRAM for KV cache, the deployment
  processes concurrent loads with 100% request success rates up to 256 concurrent users, and 99.6% success rates at the
  extreme limit of 1024 users with 16K context windows.

  ### 2. Latency Profiles

  • Ultra-Low Concurrency (1–8 users): Response times are sub-second across all context sizes except the largest window:
      • Small Context (4 – 512 tokens): ~0.11s to 0.25s
      • Medium Context (1024 – 4096 tokens): ~0.20s to 0.51s
      • Large Context (8192 – 16384 tokens): ~0.99s to 2.24s
  • Peak Concurrency (1024 users): Latency scales linearly due to queue delays:
      • Small/Medium Context (≤4K tokens): ~9.53s to 12.06s
      • Maximum Context (16K tokens): ~19.80s

  ### 3. Optimization Architecture Impact

  • Chunked Prefill ( --enable-chunked-prefill + --max-num-batched-tokens 4096 ): Prevents massive prefill requests
  from causing execution hiccups or out-of-memory (OOM) spikes. Long prompts are chunked and scheduled incrementally,
  keeping the execution flow stable.
  • Auto-Fallback KV Cache ( --kv-cache-dtype auto ): Serves the KV cache natively in bfloat16 to leverage A100 Tensor
  Cores. Because SM80 (A100) doesn't natively support Triton's FP8 KV cache, this config guarantees optimal arithmetic
  accuracy and parser stability for tool calling without sacrificing throughput.
  • Asynchronous Scheduling ( --async-scheduling ): Decouples request handling from engine execution step iterations,
  allowing high concurrency (up to 1024 users) to queue efficiently without dropping TCP connections or triggering
  gateway timeouts.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One last cross check — what are the trade-offs of the deployment?&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="gt"&gt;&amp;gt; what are the trade-offs on the deployment?&lt;/span&gt;

  The current deployment configuration for the Gemma 4 12B QAT model on Azure Container Apps NC24-A100 presents several
  engineering trade-offs:

  ### 1. Model Quantization: QAT (INT4) vs. Full Precision (bfloat16)

  • The Gain: Quantization-Aware Training compresses model weights down to 8.29 GiB (from ~24 GiB). This frees up 66.26
  GiB of VRAM for the KV cache, allowing the model to easily support 1,024 concurrent users without OOM crashes.
  • The Trade-off: There is a minor loss in perplexity compared to the full bfloat16 baseline. While QAT is
  significantly more accurate than standard Post-Training Quantization (PTQ), very complex reasoning or formatting/tool-
  calling edge cases might occasionally see slight degradation compared to the unquantized 12B model.

  ### 2. KV Cache Precision: Native bfloat16 vs. FP8 

  • The Gain: Keeping --kv-cache-dtype auto (which defaults to bfloat16 ) provides high numerical range stability. It
  completely avoids the tool-calling/reasoning parser failures or garbled outputs that can happen when forcing FP8 on
  unsupported hardware.
  • The Trade-off: It increases the VRAM footprint per token compared to FP8. Since A100 GPUs (compute capability 8.0) do
  not support Triton's native FP8 KV cache, we must spend 2 bytes per token instead of 1. If we were using newer L4 or
  H100 hardware, we could utilize FP8 to double the token capacity of the KV cache.

  ### 3. Chunked Prefill: Enabled vs. Disabled

  • The Gain: Setting --enable-chunked-prefill with --max-num-batched-tokens 4096 ensures system stability. By
  chunking large inputs, it prevents memory fragmentation and sudden OOMs when multiple users submit massive prompts
  simultaneously.
  • The Trade-off: It introduces a slight scheduling overhead for very large single-user prompts. A single prompt of 16K
  tokens must be processed in 4 successive prefill chunks, which slightly increases the Time-to-First-Token (TTFT) for
  that individual request compared to a non-chunked run.

  ### 4. GPU Scaling: Single NC24-A100 vs. Multi-GPU Tensor Parallelism

  • The Gain: Running on a single A100 GPU ( --tensor-parallel-size 1 ) minimizes inter-GPU communication overhead and
  keeps infrastructure costs lower.
  • The Trade-off: We hit a physical hardware ceiling at ~1,000 concurrent users. Scaling further requires upgrading to a
  multi-GPU workload profile (e.g., standard NC48-A100 with 2 GPUs) and enabling --tensor-parallel-size 2 (introducing
  NVLink communication latency) or scaling horizontally across multiple Container App replicas behind a load balancer.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about the other ACA option — the T4?&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="gt"&gt;&amp;gt; how does the performance compare to ACA T4?&lt;/span&gt;

  Comparing the current deployment on Azure Container Apps (NC24-A100) to a standard deployment on Azure Container Apps
  (Tesla T4) reveals significant differences in capacity, numerical stability, and throughput:

  ### 📊 Performance Comparison: A100 vs. T4

   Metric / Dimension | ACA NVIDIA A100 (80GB) | ACA Tesla T4 (16GB)
  ---------------------------|----------------------------------------|--------------------------------------------------
   VRAM Capacity | 80 GB | 16 GB
   KV Cache Allocation | 66.26 GiB | ~5.5 GiB (after loading weights &amp;amp; system overhead
   Max Concurrency Limit | 1,024 concurrent users (99.6% success) | ~8 concurrent users (OOMs beyond this)
   Max Context Window | 32,768 tokens | ~2,048 tokens (due to VRAM limits)
   Peak Throughput | ~69.2 req/s | ~4 - 6 req/s (approx. 10x slower)
   Native bfloat16 Support | Yes (Ampere hardware acceleration) | No (emulated, leading to performance drops)
  ──────
  ### 🔑 Critical Architectural Trade-offs

  #### 1. Numerical &amp;amp; Parser Stability ( bfloat16 vs. FP16 )

  • A100: Native support for bfloat16 keeps calculations stable. The model generates structured outputs and tool calls
  with perfect parse rates.
  • T4: Lacks hardware bfloat16 support. Falling back to standard float16 (FP16) causes numerical overflow/underflow,
  leading to garbled text generation or frequent tool-calling parser failures.

  #### 2. Concurrency Capacity (KV Cache Scale)

  • A100: Leaves 66.26 GiB for the KV cache, holding over 1,050,000 tokens concurrently.
  • T4: With only ~5.5 GiB left for the KV cache, a single user requesting a 4K context window will consume almost the
  entire allocation, causing immediate Out-of-Memory (OOM) failures for subsequent concurrent users.

  #### 3. Execution Kernels

  • A100: Utilizes high-performance FlashInfer and advanced Triton kernels for self-attention scaling.
  • T4: Must fall back to slower vLLM execution kernels and has a much smaller shared memory capacity. It requires custom
  Triton patches just to launch without encountering shared memory allocation errors.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  So where is the best place to run Gemma? AWS or GCP? Azure?
&lt;/h4&gt;

&lt;p&gt;Survey says…&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="gt"&gt;&amp;gt; compare aws vs gcp vs azure performance&lt;/span&gt;

 Here is a comparison of the performance characteristics of the self-hosted Gemma 4 12B QAT model across Azure, AWS, and
  GCP deployments based on the benchmark sweeps:

  ### 📊 Multi-Cloud Performance Comparison

   Metric / Feature | Azure Container Apps (… | AWS EC2 g6.2xlarge … | GCP Cloud Run (L4)
  -------------------------------------------|-------------------------|------------------------|------------------------
   GPU Model | NVIDIA A100 (80GB) | NVIDIA L4 (24GB) | NVIDIA L4 (24GB)
   VRAM Size | 80 GiB | 24 GiB | 24 GiB
   Peak Throughput (Small Context) | 69.2 req/s (256 users) | 93.3 req/s (256 | 40.3 req/s (1024
                                             | | users)&lt;span class="err"&gt;*&lt;/span&gt; | users)
   Throughput (16K Context) | 26.8 req/s | 19.9 req/s | 6.1 req/s
   Average Latency (1K Context, 1024 Users) | 11.29s | 19.90s | 23.22s
   Average Latency (16K Context, 1024 Users) | 19.80s | 26.98s | 33.78s
   16K Context Stability (at 1024 Users) | 99.6% Success Rate | 100% Success Rate | Degraded (Slight loss)
   16K Context Stability (at 2048 Users) | Not tested (capped at | 61.8% Success Rate | 22.0% Success Rate
                                             | 1024) | | (Severe OOM)

  │ [!NOTE]
  │ &lt;span class="err"&gt;*&lt;/span&gt; The AWS EC2 instance utilized FP8 KV cache quantization ( --kv-cache-dtype fp8 ) which is natively supported on the
  │ L4 GPU (SM89 architecture). The A100 GPU (SM80 architecture) utilized native bfloat16 KV cache ( --kv-cache-dtype
  auto
  │ ), yielding higher quality and parser stability but capping peak arithmetic throughput for tiny contexts.
  ──────
  ### 🔑 Core Cloud Platform Insights

  #### 1. Compute &amp;amp; Architecture Scaling

  • Azure (A100 80GB): Provides the best latency scaling and the highest throughput for large context sizes (26.8 req/s
  vs. 6.1 req/s on GCP) because of the higher raw memory bandwidth and larger VRAM buffer on the A100.
  • AWS (L4 24GB via EC2): Outperforms GCP's L4 implementation significantly. By bypassing container abstraction overhead
  (direct host OS virtualization), it achieves a peak throughput of 93.3 req/s (with FP8 cache enabled) and maintains a
  100% success rate up to 1024 users.
  • GCP (L4 24GB via Cloud Run): Suffers from serverless/container platform scheduling bottlenecks. Under high
  concurrency (≥1024 users), the container ingress and CPU throttling introduce queuing overhead, leading to high latency
  spikes (33.78s for 16K context) and scaling degradation (falling to a 22% success rate at 2048 users).

  #### 2. Cost-to-Performance Summary

  • For High-Throughput Production / Large Contexts: Azure ACA (A100) is the optimal deployment. The huge VRAM size
  allows processing over a million concurrent tokens.
  • For Low-to-Medium Concurrency Development: AWS EC2 (L4) offers the best cost-to-performance balance by leveraging
  native FP8 KV cache quantization at a much lower hourly hardware cost.
  • For Dynamic Auto-Scaling (Low Load): GCP Cloud Run (L4) provides serverless ease of deployment, but is not suited for
  high-concurrency or long-context workloads.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What about costs?!?
&lt;/h4&gt;

&lt;p&gt;The serving costs were summarized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;  Here is a cost and efficiency comparison between deploying on the serverless NC24-A100 profile versus the serverless
  NC4-T4 profile on Azure Container Apps:

  ### 💰 Cost Comparison: A100 vs. T4

   Component | ACA A100 Profile ( NC24-A100 ) | ACA T4 Profile ( NC4-T4 )
  ---------------------------------------|---------------------------------------|---------------------------------------
   GPU Specifications | 1x NVIDIA A100 (80GB VRAM) | 1x NVIDIA Tesla T4 (16GB VRAM)
   vCPU Allocation | 24 Cores | 4 Cores
   RAM Allocation | 220 GiB | 28 GiB
   GPU Rate (per second) | ~$0.000651 / sec | ~$0.000090 / sec
   GPU Hourly Cost | $2.34 | $0.32
   vCPU Hourly Cost | $2.96 | $0.49
   RAM Hourly Cost | $3.56 | $0.45
   Total Hourly Cost | $8.86 / hour | $1.26 / hour
   Total Monthly Cost (100% Active) | $6,467.80 / month | $919.80 / month
  ──────
  ### 📈 Cost Efficiency &amp;amp; Unit Economics (Under Load)

  While the T4 profile is ~7x cheaper on a pure hourly basis, it is far less cost-efficient when handling production
  traffic or long context windows:

  #### 1. Concurrency Capacity Economics

  • T4 Profile (Max ~8 concurrent users):
      • Cost per concurrent user: $1.26 / 8 = $0.1575 / user-hour
  • A100 Profile (Max ~1024 concurrent users):
      • Cost per concurrent user: $8.86 / 1024 = $0.0086 / user-hour
      • Result: The A100 is ~18x more cost-efficient per active user under high load due to its massive VRAM budget (66.
      26 GiB for KV cache) and native Ampere execution performance.

  #### 2. Large Context &amp;amp; Failure Overhead

  • T4 Profile: Incapable of serving inputs ≥4K tokens under concurrency. Attempting to do so triggers OOM failures,
  leading to wasted execution billing cycles on failed requests.
  • A100 Profile: Reliably handles up to 32K context lengths at a 100% success rate under multi-user concurrency,
  providing predictable billing and zero failure-overhead costs.
  ──────
  ### 💡 Recommendation Summary

  • &lt;span class="ge"&gt;**&lt;/span&gt;Use T4 (1.26/hr&lt;span class="se"&gt;\l&lt;/span&gt;e 2$K tokens) and code is executed single-user.
  • &lt;span class="ge"&gt;**&lt;/span&gt;Use A100 (8.86/hr&lt;span class="se"&gt;\g&lt;/span&gt;e 4$K tokens).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about other cloud costs?&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="gt"&gt;&amp;gt; compare price/performance across AWS,GCP,and Azure&lt;/span&gt;

  Here is a comparative analysis of the price-to-performance ratio across AWS, GCP, and Azure deployments when serving
  the Gemma 4 12B QAT model under maximum concurrent load:

  ### 📊 Multi-Cloud Price/Performance Metrics

   Metric | Azure Container Apps (A1… | AWS EC2 g6.2xlarge (L4) | GCP Cloud Run (L4)
  ------------------------------|---------------------------|---------------------------|--------------------------------
   Billing Model | Serverless Consumption | Dedicated Virtual Machine | Serverless Consumption (Scale-
                                | (Scale-to-0) | | to-0)
   Hourly Run Cost | $8.86 / hour | $0.97 / hour | $1.26 / hour
   Throughput (16K Context) | 26.8 requests/sec | 19.9 requests/sec | 6.1 requests/sec
   Stability (16K @ 1024 Users) | 99.6% Success | 100% Success | Degraded (Slight loss)
   Price / Throughput Ratio | $0.33 per (req/s)-hour | $0.049 per (req/s)-hour | $0.206 per (req/s)-hour
   Cost per 1,000 requests | $0.092 | $0.013 | $0.057
  ──────
  ### 🔑 Price/Performance Winners by Scenario

  #### 1. The Cost Efficiency Winner: AWS EC2 ( g6.2xlarge )

  • Why: At $0.97/hour, AWS EC2 dedicated hosting is extremely cheap compared to serverless container app environments.
  Because we can enable the FP8 KV cache natively on the L4 GPU, we maintain a 100% success rate and high throughput (19.
  9 req/s).
  • Cost Efficiency: It costs only $0.013 per 1,000 requests, making it ~7x cheaper than Azure and ~4x cheaper than GCP
  per request processed.
  • Catch: It is not serverless; you pay the flat $0.97/hour even when there is zero traffic (scale-to-zero is not
  supported out of the box without custom VM scaling logic).

  #### 2. The Raw Performance Winner: Azure Container Apps (A100)

  • Why: Azure Container Apps utilizing a datacenter-grade A100 GPU offers the highest raw throughput (26.8 req/s at 16K
  context window) and the lowest average latency under peak load (19.80s vs. GCP's 33.78s).
  • Cost Efficiency: At $0.092 per 1,000 requests, it has higher unit costs, but it supports Scale-to-Zero, meaning you
  pay $0.00 when idle.
  • Catch: The entry-level price is high when active ($8.86/hr) due to the mandatory allocation of 24 vCPUs and 220 GiB
  RAM that Azure pairs with the A100 GPU profile.

  #### 3. The Serverless L4 Option: GCP Cloud Run (L4)

  • Why: GCP Cloud Run Gen2 offers serverless GPU profiles starting at $1.26/hour while active, supporting scale-to-zero.
  • Cost Efficiency: Due to container ingress and scheduling limits under concurrency, throughput drops to 6.1 req/s for
  large contexts, pushing the unit cost to $0.057 per 1,000 requests. It is a good middle ground for low-concurrency
  workloads but lacks the efficiency of AWS or the power of Azure A100 under load.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The strategy for using MCP for Gemma 4 GPU deployment with Antigravity CLI and Azure was validated with an incremental step by step approach.&lt;/p&gt;

&lt;p&gt;A minimal stdio transport MCP Server was started from Python source code and validated with Antigravity CLI running as a MCP client in the same local environment. This Python server provided all of the management tools to deploy and troubleshoot Azure Model deployments.&lt;/p&gt;

</description>
      <category>qat</category>
      <category>gemma4</category>
      <category>mcps</category>
      <category>azurecontainerapps</category>
    </item>
    <item>
      <title>12B Gemma 4 Deployment with NVIDIA Blackwell 6000, MCP, Cloud Run, and Antigravity CLI</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Mon, 22 Jun 2026 02:44:39 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/12b-gemma-4-deployment-with-nvidia-blackwell-6000-mcp-cloud-run-and-antigravity-cli-3ckn</link>
      <guid>https://dev.clauneck.workers.dev/gde/12b-gemma-4-deployment-with-nvidia-blackwell-6000-mcp-cloud-run-and-antigravity-cli-3ckn</guid>
      <description>&lt;p&gt;This article provides a step by step deployment guide for Gemma 4 to a Google Cloud Run hosted GPU enabled system,. A suite of Python MCP tools is built to simplify management of the vLLM hosted Gemma 4 deployment with Antigravity CLI.&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.amazonaws.com%2Fuploads%2Farticles%2Fprgzxj1uzsqeaii6cvvv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprgzxj1uzsqeaii6cvvv.jpeg" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is this project trying to Do?
&lt;/h4&gt;

&lt;p&gt;This project is a DevOps/SRE assistant that uses a Gemma 4 model hosted on Cloud Run with GPU. It provides tools to provision the Docker container and deploy the model, as well as for observability and performance testing.&lt;/p&gt;

&lt;p&gt;This project is similar to a previous project that targeted GPU hosted Gemma4 instances on GCP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/gemma-sre-self-hosted-vllm-infrastructure-agent-afda000c0943" rel="noopener noreferrer"&gt;Gemma-SRE: Self-Hosted vLLM Infrastructure Agent&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Installation Instructions
&lt;/h4&gt;

&lt;p&gt;The detailed installation instructions for Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Python MCP Documentation
&lt;/h4&gt;

&lt;p&gt;The official GitHub Repo provides samples and documentation for getting started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;GitHub - modelcontextprotocol/python-sdk: The official Python SDK for Model Context Protocol servers and clients&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Where do I start?
&lt;/h4&gt;

&lt;p&gt;The strategy for starting MCP development for model management is a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;First, the basic development environment is setup with the required system variables, and a working Antigravity CLI configuration.&lt;/p&gt;

&lt;p&gt;Then, a minimal Python MCP Server is built with stdio transport. This server is validated with Antigravity CLI in the local environment.&lt;/p&gt;

&lt;p&gt;This setup validates the connection from Antigravity CLI to the local server via MCP. The MCP client (Antigravity CLI) and the Python MCP server both run in the same local environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup the Basic Environment
&lt;/h4&gt;

&lt;p&gt;At this point you should have a working Python environment and a working Antigravity CLI installation. The next step is to clone the GitHub samples repository with support scripts:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/xbill9/gemma4-tips
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;init.sh&lt;/strong&gt; from the cloned directory.&lt;/p&gt;

&lt;p&gt;The script will attempt to determine your shell environment and set the correct variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpu-12B-6000-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session times out or you need to re-authenticate- you can run the &lt;strong&gt;set_env.sh&lt;/strong&gt; script to reset your environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpu-12B-6000-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Management Tool with MCP Stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the standard MCP libraries provide is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;The connection over stdio will look similar to this:&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;# Initialize FastMCP server
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Self-Hosted vLLM DevOps Agent&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;h4&gt;
  
  
  Running the Python Code
&lt;/h4&gt;

&lt;p&gt;First- switch the directory with the Python version of the MCP sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/gemma4-tips/gpu-12B-6000-devops-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the release version on the local system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nb"&gt;install
&lt;/span&gt;Processing ./.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project can also be linted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/gpu-12B-6000-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make lint
ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
All checks passed!
ruff format &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
6 files already formatted
mypy &lt;span class="nb"&gt;.&lt;/span&gt;
Success: no issues found &lt;span class="k"&gt;in &lt;/span&gt;6 &lt;span class="nb"&gt;source &lt;/span&gt;files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MCP stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the MCP protocol provides is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;In this project Antigravity CLI is used as the MCP client to interact with the Python MCP server code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI mcp_config.json
&lt;/h4&gt;

&lt;p&gt;A sample MCP server file is provided in the .agents directory:&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gpu-devops-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/home/xbill/gemma4-tips/gpu-12B-6000-devops-agent/server.py"&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;"env"&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;"GOOGLE_CLOUD_PROJECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aisprint-491218"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_CLOUD_LOCATION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-central1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"VLLM_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gpu-12b-6000-devops-agent-289270257791.us-central1.run.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MODEL_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/models/gemma-4-12B-it"&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;h4&gt;
  
  
  Validation with Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;The final connection test uses Antigravity CLI as a MCP client with the Python code providing the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
&amp;gt; ✓ google-dev-knowledge Tools: search_documents, answer_query, get_documents
   ✓ gpu-26b-6000-devops-agent Tools: save_hf_token, get_vllm_endpoint, list_vertex_models, list_bucket_models,
                                analyze_cloud_logging, +19 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting Started with Gemma 4 on GPU
&lt;/h4&gt;

&lt;p&gt;The Official vLLM repo also has Gemma4 specific information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm/releases" rel="noopener noreferrer"&gt;Releases · vllm-project/vllm&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Lifecycle Management via MCP
&lt;/h4&gt;

&lt;p&gt;The MCP tools provide a complete suite of agent-oriented operations for managing vLLM deployment on Cloud Run or a TPU.&lt;/p&gt;

&lt;p&gt;The MCP server tools are used to deploy the model to Cloud Run with TPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy the model

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Checking System status
&lt;/h4&gt;

&lt;p&gt;The status can be checked with an MCP tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;● gpu-devops-agent/get_model_details(Model details check) (ctrl+o to expand)

  The vLLM service gpu-12b-6000-devops-agent is Healthy and fully operational.

  ### 🌀 Current vLLM Status

  • Service Status: 🟢 Ready
  • vLLM Engine Health: ✅ Healthy
  • Active Model: gpu-12b-6000-devops-agent 
  • Revision: gpu-12b-6000-devops-agent-00006-kbs 
  • Endpoint: https://gpu-12b-6000-devops-agent-wgcq55zbfq-uc.a.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross Check The Deployed Model
&lt;/h4&gt;

&lt;p&gt;Once the model starts locally- the MCP tool allows for model verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● gpu-devops-agent/verify_model_health(Model health verification) (ctrl+o to expand)

  The gpu-12b-6000-devops-agent service is Online and functioning correctly.
  ### Service Status Summary
  • Cloud Run Status: 🟢 Ready
  • vLLM Health: 🟢 Online
  • Model Health Check: ✅ Passed
      • Latency: 0.77 seconds
      • Endpoint: https://gpu-12b-6000-devops-agent-wgcq55zbfq-uc.a.run.app
  • Latest Revision: gpu-12b-6000-devops-agent-00006-kbs 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; get_model_details
● gpu-devops-agent/get_model_details(Model details check) (ctrl+o to expand)

  ### 🧩 Model Details Summary

  The vLLM engine is currently serving the following model:

  • Model ID: gpu-12b-6000-devops-agent 
  • Owned By: vllm 
  • Health Status: ✅ Healthy 
  • Active Endpoint: https://gpu-12b-6000-devops-agent-289270257791.us-central1.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; &amp;gt; get_system_details

8.1k in | 829 out | 424 thinking
● Read(/home/xbill/.gemini/antigravity-cli/brain/...e05/.system_generated/steps/160/output.txt) (ctrl+o to expand)
  ### 🖥️ System Details: gpu-12b-6000-devops-agent 
  The service is currently running on Google Cloud Run (Gen2) with the following hardware and software
  configuration:

  #### 🏗️ Infrastructure &amp;amp; Resources
  • GPU Type: nvidia-rtx-pro-6000 (1 unit)
  • CPU: 20 vCPUs (with CPU Boost enabled)
  • Memory: 80 GiB RAM
  • Execution Environment: gen2 
  • Scaling: Min: 1 instance | Max: 3 instances
  • Region: us-central1 

  #### 🧠 vLLM Engine Configuration

  • Model Path: /mnt/models/gemma-4-12B-it-text-fp8 
  • Context Window: 131,072 tokens
  • Quantization: fp8 
  • KV Cache Dtype: fp8 
  • GPU Memory Utilization: 95% ( 0.95 )
  • Features Enabled:
      • enable-chunked-prefill 
      • enable-prefix-caching 
      • enable-auto-tool-choice (with gemma4 parser)

  #### 📈 Live Resource Metrics

  • vLLM Health: 🟢 Online 
  • Resident Memory: ~3.0 GB
  • Virtual Memory: ~24.8 GB
  • Open File Descriptors: 65 (Max: 25,000)
  • KV Cache Usage: 0% (Idle between benchmark steps)
  • Prefix Cache Hits: 308M tokens (High hit rate due to repetitive benchmark prompts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the Cloud Run deployment status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show the cloud run docker status

12.1k in | 841 out | 525 thinking
● Read(/home/xbill/.gemini/antigravity-cli/brain/...3fe05/.system_generated/tasks/task-166.log) (ctrl+o to expand)

  ### 🐳 Cloud Run Container ("Docker") Status

  The service gpu-12b-6000-devops-agent is running with the following container configuration:

  #### 🖼️ Image &amp;amp; Entrypoint
  • Image: vllm/vllm-openai:latest 
  • Command: bash 
  • Arguments:
    -c "vllm serve /mnt/models/gemma-4-12B-it-text-fp8 --served-model-name gpu-12b-6000-devops-agent --enable-
  log-requests --enable-chunked-prefill --enable-prefix-caching --generation-config auto --enable-auto-tool-
  choice --tool-call-parser gemma4 --reasoning-parser gemma4 --dtype bfloat16 --quantization fp8 --kv-cache-dtype
  fp8 --max-num-seqs 8 --gpu-memory-utilization 0.95 --tensor-parallel-size 1 --load-format runai_streamer --port
  8080 --host 0.0.0.0 --max-model-len 131072"

  #### ⚙️ Runtime Environment

  • Container Port: 8080 
  • Concurrency: 16 (max simultaneous requests per container)
  • Timeout: 3600s (1 hour)
  • CPU Throttling: false (Always allocated)
  • Startup CPU Boost: true 

  #### 🩺 Health &amp;amp; Readiness

  • Startup Probe: tcpSocket on port 8080 
      • Initial Delay: 240s 
      • Failure Threshold: 120 
      • Period: 15s 
  • Service Readiness: ✅ Ready: True (Last transition: 2026-06-21T23:10:27Z)

  #### 📦 Storage &amp;amp; Volumes

  • Volume: model-volume (Cloud Storage GCSFuse)
  • Bucket: aisprint-491218-bucket 
  • Mount Path: /mnt/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Check the Cloud Run Service
&lt;/h4&gt;

&lt;p&gt;Once Deployed — the model is visible as a standard Cloud Run Service:&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%2Fi2fikyq1gg6fexuznyml.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%2Fi2fikyq1gg6fexuznyml.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Review the Model
&lt;/h4&gt;

&lt;p&gt;Check the model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the model details?

  The gpu-12b-6000-devops-agent is a specialized SRE/DevOps assistant running a self-hosted Gemma 4 12B model.
  ### 🧩 Model Configuration Details

   Detail | Configuration
  ---------------------------------------------|-----------------------------------------------------------------
   Model ID | google/gemma-4-12B-it (served as gpu-12b-6000-devops-agent )
   Quantization | fp8 (Weights) / fp8 (KV Cache)
   Context Window | 131,072 tokens
   GPU Hardware | NVIDIA RTX PRO 6000 (Cloud Run Gen2)
   Serving Engine | vLLM (with gemma4 reasoning and tool-call parsers)
   Key Features | Chunked prefill, Prefix caching, Auto-tool choice
   VRAM Usage | 95% ( 0.95 utilization)

  ### 🛠️ Active Capabilities

  The model is optimized for Zero-shot SRE diagnostics and infrastructure management. It has native support for:

  • Structured Reasoning: It uses a dedicated reasoning parser to output step-by-step SRE remediation plans.
  • Function Calling: Automatically selects tools for log analysis ( analyze_gpu_logs ), scaling (
  update_vllm_scaling ), and system monitoring.
  • Performance: Currently undergoing a high-concurrency benchmark sweep to validate latency across its full
  context window.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is online — you can send queries to the Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● gpu-devops-agent/query_gemma4_with_stats(Gemma 4 query with stats) (ctrl+o to expand)

  Gemma is a family of lightweight, state-of-the-art open models built by Google DeepMind, sharing the same
  technology and research used to create the Gemini models.

  ### 📊 Performance Statistics

  The query was processed by the self-hosted gpu-12b-6000-devops-agent with the following metrics:

  • Tokens per Second: 81.20 tokens/s 
  • Time to First Token (TTFT): 0.539s 
  • Total Generation Time: 8.186s 
  • Total Tokens: ~621 

  ### 💬 Model Response Summary

  • "Open" Architecture: Unlike closed models (like GPT-4), Gemma's weights are public, allowing developers to
  download, customize, and run it locally.
  • Efficient &amp;amp; Capable: Designed to run on consumer hardware (laptops, mobile) while maintaining high
  performance.
  • Safety-Tuned: Built with Google's rigorous safety standards to ensure helpful and safe interactions.
  • Flexible Sizes: Available in various parameter counts (e.g., 2B, 7B, 27B) to balance speed and power.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benchmark the Local Model
&lt;/h4&gt;

&lt;p&gt;The local model was benchmarked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I have completed the benchmark sweep and generated a full performance report.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The analysis is reviewed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;gt; summarize the 12B on 6000 results

  The benchmark results for the Gemma 4 (12B) model on the NVIDIA RTX PRO 6000 (Blackwell) hardware demonstrate a
  massive performance leap, particularly in high-concurrency and high-context stability.
  ### 🚀 12B Blackwell Performance Snapshot

   Metric | Low Load (1-8 Users) | Medium Load (64 Users) | Peak Load (256 Users)
  ---------------------------|---------------------------|---------------------------|---------------------------
   Throughput (Req/s) | ~3 - 24 Req/s | ~72.6 Req/s | ~88.3 Req/s 🚀
   Avg Latency (s) | 0.2s - 0.4s | 0.6s - 1.0s | 1.6s - 4.5s
   Success Rate | 100% | 100% | 100%
  ──────
  ### 🔍 Key Result Highlights

  #### 1. Throughput: 3.8x Faster than L4
  The RTX 6000 Blackwell reached a peak throughput of 88.3 Requests/second (at 256 users, 128 context). For
  comparison, the NVIDIA L4 GPU saturates at 23.5 RPS for the same model. This 3.8x improvement allows a single
  Blackwell instance to handle nearly 4x the request density of an L4.
  #### 2. Context Mastery (Up to 16,384 Tokens)

  Thanks to the 96GB GDDR7 VRAM, the 12B model maintains high throughput even as the context window scales:
  • 4,096 tokens: Maintained 75.1 Req/s at peak concurrency.
  • 8,192 tokens: Maintained 57.4 Req/s at peak concurrency.
  • 16,384 tokens: Delivered 31.5 Req/s at peak concurrency with 100% success.
  • Note: On the 24GB L4 GPU, 16k context windows typically cause OOM (Out of Memory) errors or 0 RPS.
  #### 3. Sub-Second Latency Floor
  The Blackwell architecture provides an extremely low latency floor for real-time SRE applications:

  • Small Context ( &amp;lt; 512 tokens): Average latency stays under 1 second for up to 128 concurrent users.
  • Medium Context (4,096 tokens): Average latency stays under 2 seconds for up to 256 concurrent users.
  ──────
  ### 📊 Comparative Scaling Table (RTX 6000 Blackwell)

   Context Size | 1 User (Latency) | 32 Users (Req/s) | 256 Users (Req/s)
  ---------------------------|---------------------------|---------------------------|---------------------------
   128 tokens | 0.23s | 52.8 Req/s | 88.3 Req/s
   1,024 tokens | 0.22s | 58.9 Req/s | 78.4 Req/s
   4,096 tokens | 0.41s | 46.4 Req/s | 75.1 Req/s
   16,384 tokens | 0.78s | 21.3 Req/s | 31.5 Req/s

  │ [!IMPORTANT]
  │ Summary Verdict: Moving the 12B model to the Blackwell RTX 6000 transforms it from a "fast chat" model into a
  │ "high-density automation" engine. It can process massive streams of log data (16k context) at 30+ RPS,
  │ something previously only possible on enterprise TPU clusters.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is visualized:&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%2Frjqt6gj9ypjcc0qzpgpe.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%2Frjqt6gj9ypjcc0qzpgpe.png" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How does this compare to other Hardware Options?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; compare to other hardware options at 12B
6.9k in | 728 out | 472 thinking
● Read(/home/xbill/gemma4-tips/tpu-12B-v6e1-devops-agent/grid_benchmark_results.csv) (ctrl+o to expand)

  The Gemma 4 (12B) model shows a clear performance hierarchy across hardware, with the RTX 6000 Blackwell (96GB)
  serving as a highly balanced, high-density workhorse compared to the specialized TPU v6e and the standard
  NVIDIA L4.

  ### 📊 12B Hardware Comparison Matrix

   Metric | NVIDIA L4 (24GB) | RTX 6000 Blackwell (96GB) | TPU v6e-1 (Trillium)
  ---------------------------|---------------------------|---------------------------|---------------------------
   Peak Throughput | ~23.5 RPS | ~88.3 RPS 🚀 | ~72.8 RPS
   Success Rate (16k Ctx) | 0% (OOM) | 100% (up to 512 users) | 100% (up to 2048 users)
   Avg Latency (Short Ctx) | 0.2s - 0.5s | 0.2s - 0.4s | 0.2s - 0.4s
   Max Context Stability | ~8k tokens | 131k tokens | 4k - 8k tokens*

  *Note: In the TPU v6e-1 sweep, performance dropped significantly after 4k context, with 8k and 16k contexts
  failing (0% success) due to the specific memory allocation/sharding on a single chip.
  ──────
  ### 🔍 Comparative Insights
  #### 1. RTX 6000 Blackwell vs. TPU v6e-1 (Trillium)
  • Throughput Leadership: Surprisingly, the Blackwell RTX 6000 delivered higher peak throughput (~88 RPS) than
  the single-chip TPU v6e-1 (~72 RPS) for the 12B model. This highlights the massive bandwidth of GDDR7 (1.79
  TB/s) on Blackwell.
  • Context Ceiling: The RTX 6000 (96GB) is far more resilient for long-context tasks. While the TPU v6e-1 began
  failing at 8k tokens, the Blackwell GPU handled up to 16k context with 100% success and is capable of scaling
  up to 131k context with reduced concurrency.

  #### 2. Blackwell vs. NVIDIA L4

  • 4x Density Jump: The Blackwell GPU provides a 3.8x throughput increase over the L4. This means a single RTX
  6000 instance can replace four L4 instances while maintaining better latency.
  • Reliability: The L4 is strictly limited to low-context, low-concurrency tasks. The Blackwell card removes
  these constraints, allowing enterprise-grade automation to run on a single card.
  ──────
  ### ⚖️ Strategic Recommendation for 12B

  • 🏆 The Gold Standard: RTX 6000 Blackwell
      • Why: Best-in-class throughput (88 RPS) and the highest stability for long-context (16k-128k) workloads.
      It is the most versatile choice for any 12B DevOps deployment.
  • 🥈 The Budget Choice: NVIDIA L4
      • Why: Ideal for individual developer use or low-traffic SRE sidecars where context length stays below 8k.
  • 🥉 Specialized Scaling: TPU v6e-4/8
      • Why: While a single v6e-1 chip is comparable to a Blackwell card, moving to a v6e-4 or v6e-8 cluster
      would drastically exceed GPU performance for massive batch analysis (as seen in the 31B results).

  Summary Result: For the Gemma 4 (12B) model, the RTX 6000 Blackwell is the most performant and stable single-
  accelerator option currently available in the fleet.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about other Gemma Model sizes?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; how does this compare to the 2B,4B,26B,31B across hardware deployments

31.4k in | 553 out | 306 thinking
● Read(/home/xbill/gemma4-tips/gpu-4B-L4-devops-agent/benchmark_report.md) (ctrl+o to expand)

  This final cross-matrix compares the Gemma 4 family across all model sizes and hardware platforms. The results
  highlight the NVIDIA RTX PRO 6000 (Blackwell) as the strongest single-card performer, while the TPU v6e
  provides unmatched scale for the largest models.
  ### 🏆 Master Cross-Model Hardware Comparison

   Model Size | NVIDIA L4 (24GB) | RTX 6000 Blackwell (96GB) | TPU v6e (Trillium) | Best Fit
  ------------------|------------------|---------------------------|-------------------------|-------------------
   2B | ~140 tokens/s | ~210 tokens/s | - | Edge / Mobile SRE
   4B | ~90 tokens/s | ~120 tokens/s | - | Real-time Chat
   12B | ~23 RPS | ~88 RPS 🚀 | ~73 RPS (v6e-1) | SRE Standard
   26B | ~131 tokens/s | ~1,232 tokens/s ⚡ | ~445 tokens/s (v6e-4) | High-Density SRE
   31B | ~140 tokens/s | ~345 tokens/s | ~8,500 tokens/s (v6e-8) | Enterprise Audit
  ──────
  ### 🔍 Architectural Comparisons by Model Tier
  #### 1. Small Models (2B &amp;amp; 4B)
  • Performance: These models are extremely efficient on all hardware.
  • Hardware Preference: RTX 6000 Blackwell is the winner due to its superior GDDR7 bandwidth, delivering roughly
  30% better throughput than the L4 for these lightweight tasks.
  #### 2. Medium Models (12B - The "SRE Standard")
  • The Blackwell Revolution: The jump from L4 to Blackwell for the 12B model is the most dramatic in the fleet
  (3.8x throughput increase).
  • Context Stability: Blackwell handles 16k context with 100% success at 512 concurrent users, while the L4
  saturates early and the TPU v6e-1 begins to degrade past 4k tokens.

  #### 3. Large Models (26B &amp;amp; 31B)

  • 26B Performance: The RTX 6000 Blackwell is surprisingly optimized for the 26B model, reaching 1,232 tokens/s.
  This outperforms the TPU v6e-4 for this specific model size.
  • 31B Throughput King: The TPU v6e-8 is the undisputed leader for 31B, delivering 8.5k tokens/s. This is 25x
  faster than the Blackwell GPU, proving that TPU sharding scales exponentially better for ultra-large parameter
  counts.
  ──────
  ### ⚖️ Strategic Deployment Recommendation

   Deployment Goal | Hardware | Model
  -------------------------------------|------------------------------------|------------------------------------
   Fastest Interactive SRE Chat | RTX 6000 Blackwell | Gemma 4 26B
   Most Stable 16k+ Log Analysis | RTX 6000 Blackwell | Gemma 4 12B
   Global Enterprise Batch Audit | TPU v6e-8 (Trillium) | Gemma 4 31B
   Cost-Effective Sidecar Monitor | NVIDIA L4 | Gemma 4 4B

  │ [!IMPORTANT]
  │ Summary: For any task requiring long context (16k-131k tokens) or high concurrency (100+ users) on a single
  │ instance, the RTX 6000 Blackwell (96GB) is the most versatile and performant accelerator in the current fleet.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cost Breakdowns
&lt;/h4&gt;

&lt;p&gt;Model serving costs were compared:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; do a cost analysis

6.5k in | 373 out | 302 thinking
● Read(/home/xbill/gemma4-tips/gpu-31B-6000-devops-agent/cost_per_token_analysis.md) (ctrl+o to expand)

  This cost analysis evaluates the financial efficiency of Gemma 4 across the fleet, factoring in both the
  machine-hour rates and the token throughput measured in our benchmarks.

  ### 💵 Hardware Cost Foundations (GCP Tier 1)
   Resource | Hourly Rate (Est.) | Billing Model | Best For
  --------------------------------|--------------------|----------------------------|----------------------------
   NVIDIA L4 (Cloud Run) | ~$0.88 / hr | Per-second (Scale-to-zero) | Spiky, low-traffic SRE task
   RTX 6000 Blackwell (Cloud Run) | ~$3.76 / hr | Per-second (Scale-to-zero) | Interactive, high-context S
   TPU v6e-1 (Single Chip) | ~$1.38 / hr | On-demand (Always-on) | Steady production 12B servi
   TPU v6e-8 (Full Pod) | ~$11.00 / hr | On-demand (Always-on) | Enterprise-scale 31B batchi
  ──────
  ### 📈 Cost Per Million (CPM) Tokens (at Peak Concurrency)
  Below is the cost efficiency for generating 1 million tokens across different tiers.

  #### 1. The 12B Tier (The SRE Sweet Spot)

   Hardware | Peak Tokens/s | Cost Per Million (CPM) | Verdict
  ---------------------|---------------------|------------------------|------------------------------------------
   TPU v6e-1 | ~9,300 t/s | $0.04 | Winner for 24/7 throughput
   NVIDIA L4 | ~3,000 t/s | $0.08 | Winner for scale-to-zero
   RTX 6000 Blackwell | ~11,300 t/s | $0.09 | Best performance/dollar for high-context
  #### 2. The 26B / 31B Tier (Enterprise Intelligence)

   Hardware | Peak Tokens/s | Cost Per Million (CPM) | Verdict
  --------------------------|------------------------|------------------------|----------------------------------
   TPU v6e-8 (31B) | ~8,500 t/s | $0.36 | Global leader in efficiency
   RTX 6000 Blackwell (26B) | ~1,232 t/s | $0.85 | High-performance interactive pro
   RTX 6000 Blackwell (31B) | ~345 t/s | $3.03 | Premium context, niche use
  ──────
  ### 🔍 Financial Insights
  #### 1. The "Scale-to-Zero" Multiplier

  While the TPU v6e-1 has the lowest theoretical CPM ($0.04), it costs $33.12/day even if idle. In contrast, an
  NVIDIA L4 on Cloud Run costs $0.00 if idle.
  • Threshold: If your SRE agent is active for less than ~2 hours per day, the L4 or Blackwell GPU on Cloud Run
  is significantly cheaper than a TPU VM.

  #### 2. The High-Context Premium

  The RTX 6000 Blackwell is the only card that maintains its throughput efficiency as context grows.

  • 16k Context Analysis: While other cards OOM or drop throughput by 90%, Blackwell remains stable. Its CPM for
  a 16k context audit is effectively 8x lower than an L4 (which would require multiple retries and smaller
  shards).

  #### 3. Enterprise Batching Winner

  For massive 31B workloads, the TPU v6e-8 is essentially unbeatable. Its CPM of $0.36 is nearly 10x cheaper than
  running the same model on a single Blackwell GPU for batch tasks.

  ### ⚖️ Final Summary

  • 🏆 Best Budget SRE (Spiky Load): NVIDIA L4 ($0.08 CPM).
  • 🚀 Best High-Performance SRE: RTX 6000 Blackwell ($0.09 CPM + Ultra-low Latency).
  • 👑 Best Enterprise Throughput: TPU v6e-8 ($0.36 CPM for 31B).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The strategy for using MCP for Gemma 4 GPU deployment with Antigravity CLI was validated with a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;A minimal stdio transport MCP Server was started from Python source code and validated with Antigravity CLI running as a MCP client in the same local environment. This Python server provided all of the management tools to deploy and troubleshoot Cloud Run Model deployments.&lt;/p&gt;

</description>
      <category>gemma4</category>
      <category>mcps</category>
      <category>antigravity</category>
      <category>blackwell</category>
    </item>
    <item>
      <title>[Gemini API in Action] Building MemeFinder: A Native Mac Menu Bar Widget for Finding Memes via Text Using Gemini Vision &amp; Semantic Embeddings</title>
      <dc:creator>Evan Lin</dc:creator>
      <pubDate>Mon, 22 Jun 2026 00:41:17 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/gemini-api-hands-on-59dc</link>
      <guid>https://dev.clauneck.workers.dev/gde/gemini-api-hands-on-59dc</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhh50l2jdcckul8cmidwl.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%2Fhh50l2jdcckul8cmidwl.png" alt="image-memefinder-hero" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The Origin: Mid-Conversation, Where on Earth Is That Meme?
&lt;/h1&gt;

&lt;p&gt;Anyone who chats a lot has a folder full of memes on their phone and computer, but the moment you actually need one — the conversation is rolling, you want to drop a "thanks but no thanks" or an "I'm trash" reaction — you can't find it. The filename is &lt;code&gt;IMG_4821.jpg&lt;/code&gt;, the photo library has no categories, and search is a non-starter.&lt;/p&gt;

&lt;p&gt;I first came across a wonderful open-source project, &lt;a href="https://github.com/ShiQu1218/MemeTalk" rel="noopener noreferrer"&gt;ShiQu1218/MemeTalk&lt;/a&gt;. It builds a local meme semantic-search system with Python + Streamlit + SQLite: it scans your local meme folder, indexes images with OCR and vector embeddings, then does multi-route retrieval. Feature-complete, but research-oriented and requires opening a browser to run Streamlit.&lt;/p&gt;

&lt;p&gt;What I wanted was something closer to an "everyday handy tool":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A native Mac app, one search box. I type what I'm looking for and the relevant meme pops up. Click it and it's copied straight to the clipboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So MemeFinder was born. This post records its journey from zero to "menu-bar resident + global hotkey," and several representative pitfalls along the way.&lt;/p&gt;




&lt;h1&gt;
  
  
  System Design and Architecture
&lt;/h1&gt;

&lt;p&gt;The core concept is simple: &lt;strong&gt;point at a local meme folder → have Gemini build an index for each image → type to do a semantic search → click to copy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I made three key technical decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native SwiftUI app&lt;/strong&gt;, not Electron. Copying images to the clipboard, global hotkeys, menu-bar residency — with AppKit these are all first-class citizens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; does two things: the vision model &lt;code&gt;gemini-3-flash-preview&lt;/code&gt; reads the text in each image and generates a Traditional Chinese description plus emotion tags; &lt;code&gt;gemini-embedding-2&lt;/code&gt; turns that semantics into a 768-dimensional vector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid semantic-vector + keyword search.&lt;/strong&gt; Pure keyword recall for Chinese is too poor; only semantic vectors achieve "type a related description and find the image."&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  System Architecture Flow
&lt;/h3&gt;

&lt;p&gt;The project is deliberately split into two Swift Package targets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Contents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MemeFinder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;library&lt;/td&gt;
&lt;td&gt;Logic, models, services, ViewModels (all unit-tested)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MemeFinderApp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;executable&lt;/td&gt;
&lt;td&gt;SwiftUI views + menu-bar shell (thin layer, depends on the library)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This split isn't decorative — it directly determines whether the tests can run smoothly, as "Pitfall #2" will explain.&lt;/p&gt;




&lt;h1&gt;
  
  
  Core Implementation
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1. Auto-tagging memes with the Gemini vision model
&lt;/h3&gt;

&lt;p&gt;During indexing, each image is sent to the vision model with a request to &lt;strong&gt;output only JSON&lt;/strong&gt;: the text in the image, a Traditional Chinese description, tags, and emotion. &lt;code&gt;responseMimeType&lt;/code&gt; is set to &lt;code&gt;application/json&lt;/code&gt; to keep the output format stable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;annotateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;imageData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;URLRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
    你是迷因圖標註助手。請閱讀這張圖，輸出 JSON，欄位：
    ocr_text(圖中所有文字), description(用繁體中文描述畫面與梗),
    tags(3-8 個繁體中文關鍵字陣列), emotion(單一情緒詞)。只輸出 JSON。
    """&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&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;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
            &lt;span class="s"&gt;"parts"&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;"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="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"inline_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"mime_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imageData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64EncodedString&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;"generationConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"responseMimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;// ... set URL, x-goog-api-key header, POST body&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Hybrid semantic + keyword ranking
&lt;/h3&gt;

&lt;p&gt;After the query string is embedded into a vector, we compute cosine similarity for every image, then add weight for keywords that hit the OCR text and tags, and merge-sort:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;queryText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;IndexedImage&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queryText&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lowercased&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;whereSeparator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isWhitespace&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compactMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cosineSimilarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;haystack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ocrText&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;separator&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="nf"&gt;lowercased&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;haystack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$0&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="n"&gt;count&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;boost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// keyword boost capped at 0.3&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;boost&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&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 whole search engine is a pure function, with Gemini hidden behind a protocol, so this logic can be fully unit-tested offline without hitting the real API.&lt;/p&gt;




&lt;h1&gt;
  
  
  Major Pitfalls and Solutions
&lt;/h1&gt;

&lt;p&gt;The real time sink in this project was never the happy path — it was the pitfalls below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall #1: The mysterious &lt;code&gt;GeminiError error 0&lt;/code&gt; — indexing and search both fail
&lt;/h3&gt;

&lt;p&gt;App packaged, key set, folder chosen, hit search — and nothing shows below, just &lt;code&gt;GeminiError error 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rather than guessing, I hit the embedding endpoint once with a real key and printed the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2:embedContent"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-goog-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"content":{"parts":[{"text":"貓"}]},"output_dimensionality":768}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evidence was unmistakable:&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;"embedding"&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;"values"&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="mf"&gt;-0.0063&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.0200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;The problem: my parser was reading the &lt;strong&gt;plural&lt;/strong&gt; &lt;code&gt;embeddings[0].values&lt;/code&gt; (that's the &lt;code&gt;batchEmbedContents&lt;/code&gt; batch-endpoint format), but the single &lt;code&gt;embedContent&lt;/code&gt; call returns the &lt;strong&gt;singular&lt;/strong&gt; &lt;code&gt;embedding.values&lt;/code&gt;. So &lt;strong&gt;every embed call failed&lt;/strong&gt; — indexing each image failed, embedding the query string failed — all throwing &lt;code&gt;badResponse&lt;/code&gt; (shown in the UI as &lt;code&gt;GeminiError error 0&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Solution]&lt;/strong&gt;&lt;br&gt;
Fix the parser to read the singular &lt;code&gt;embedding.values&lt;/code&gt;, keeping the plural format as a fallback; I also hardened the annotation parser (a thinking model sometimes returns a textless "thought" part first, so skip to the first part that actually has text):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromEmbedContent&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;JSONSerialization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;with&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="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&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;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;GeminiError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;badResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cannot parse embedContent payload"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// A single embedContent returns {"embedding":{"values":[...]}}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&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;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
       &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"values"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Double&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;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// batchEmbedContents is {"embeddings":[{"values":[...]}]} — tolerate it too&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"embeddings"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[[&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;Any&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
       &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;"values"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Double&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;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;GeminiError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;badResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cannot parse embedContent payload"&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;Lesson: &lt;strong&gt;trust the actual API response over your memory or secondhand docs.&lt;/strong&gt; A single line of &lt;code&gt;curl&lt;/code&gt; saved countless guesses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall #2: SwiftPM's &lt;code&gt;main&lt;/code&gt; entry-point conflict and the SwiftUICore linking error
&lt;/h3&gt;

&lt;p&gt;I initially made the whole project a single &lt;code&gt;executableTarget&lt;/code&gt; with the tests depending on it directly. The result: tests failed to link no matter what. An executable target needs a &lt;code&gt;main&lt;/code&gt; entry point, but that entry point only exists at the UI step's &lt;code&gt;@main App&lt;/code&gt;; and casually adding a placeholder &lt;code&gt;main.swift&lt;/code&gt; then conflicts with &lt;code&gt;@main&lt;/code&gt; (Swift doesn't allow two entry points in one target). Worse, SwiftUI in an executable target spews &lt;code&gt;SwiftUICore.tbd ... not an allowed client&lt;/code&gt; linker warnings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Root cause analysis and solution]&lt;/strong&gt;&lt;br&gt;
This is actually an architecture problem, not a compilation problem. The right approach is to split the project into two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MemeFinder&lt;/code&gt; (library target)&lt;/strong&gt;: all logic, models, services, ViewModels — the tests depend only on this layer, it has no entry point, and it links cleanly as a library. ViewModels &lt;code&gt;import Combine&lt;/code&gt; (not SwiftUI) to get &lt;code&gt;ObservableObject&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MemeFinderApp&lt;/code&gt; (executable target)&lt;/strong&gt;: only SwiftUI views and &lt;code&gt;@main&lt;/code&gt;, with &lt;code&gt;import MemeFinder&lt;/code&gt; to use the public types above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the split, the library and tests don't touch SwiftUI at all, the linker warnings disappear, and the &lt;code&gt;@main&lt;/code&gt; conflict no longer exists. &lt;strong&gt;"What the tests need to depend on" often forces out clean module boundaries.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Pitfall #3: Parallel indexing's rate limit and "I want to stop indexing halfway"
&lt;/h3&gt;

&lt;p&gt;The first version indexed one image at a time, serially calling Gemini (annotate then embed). For hundreds of images this was painfully slow. So I switched to bounded parallelism with &lt;code&gt;withTaskGroup&lt;/code&gt; (at most 4 at once), which brought three new problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Gemini free tier has a &lt;strong&gt;rate limit&lt;/strong&gt; — too much concurrency triggers 429.&lt;/li&gt;
&lt;li&gt;The user wants to &lt;strong&gt;cancel&lt;/strong&gt; halfway through a large folder.&lt;/li&gt;
&lt;li&gt;Parallel completion order is chaotic, but the results need &lt;strong&gt;stable sorting&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;[Solution]&lt;/strong&gt;&lt;br&gt;
Handle the three problems separately, all converging in the same &lt;code&gt;buildIndex&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;429 backoff retry&lt;/strong&gt;: retry only &lt;code&gt;GeminiError.rateLimited&lt;/code&gt; with exponential backoff (max 3 attempts); other errors are recorded without retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cooperative cancellation&lt;/strong&gt;: honor &lt;code&gt;Task.isCancelled&lt;/code&gt;; on cancel, stop scheduling new work and keep the completed portion. Even the backoff &lt;code&gt;Task.sleep&lt;/code&gt; lets &lt;code&gt;CancellationError&lt;/code&gt; propagate normally instead of swallowing it and firing one more API call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable sorting&lt;/strong&gt;: collect results into a &lt;code&gt;[path: image]&lt;/code&gt; dictionary, then reassemble the output in the order of the pre-sorted file list, decoupled from completion order.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Seed maxConcurrent tasks first, then refill one per completion — strictly cap concurrency&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;maxConcurrent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;scheduleNext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;res&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;resultsByPath&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scheduleNext&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;Incidentally, the HTTP status code was also extracted into a pure function &lt;code&gt;mapResponse(data:statusCode:)&lt;/code&gt;: 429 → &lt;code&gt;rateLimited&lt;/code&gt;, other non-2xx → &lt;code&gt;httpError(code)&lt;/code&gt;, 2xx → return the data. The retry logic then has a basis, and this part is easy to test too.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pitfall #4: Evolving from a "windowed app" into "menu-bar resident + global hotkey"
&lt;/h3&gt;

&lt;p&gt;Whether a tool is pleasant to use comes down to "how many steps to summon it." I wanted to hit &lt;strong&gt;⌃⌘M&lt;/strong&gt; mid-conversation to bring up the search popover, with the app tucked into the menu bar, not occupying the Dock. This step hit two classic macOS pitfalls:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(a) Does a global hotkey need accessibility permission?&lt;/strong&gt; No. Use Carbon's &lt;code&gt;RegisterEventHotKey&lt;/code&gt; to register a fixed hotkey, which doesn't need Accessibility permission (unlike monitoring the whole keyboard). But under Swift 6 strict concurrency, the C event callback has to dispatch through a static &lt;code&gt;id → instance&lt;/code&gt; registry, requiring &lt;code&gt;nonisolated(unsafe)&lt;/code&gt; and relying on the invariant that "Carbon events are delivered on the main thread" for safety. If ⌃⌘M is already taken, &lt;code&gt;RegisterEventHotKey&lt;/code&gt; returns failure — in which case we silently degrade, log a line, and the menu-bar icon still works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(b) The timing race in the menu-bar right-click menu.&lt;/strong&gt; The initial approach was "set &lt;code&gt;statusItem.menu&lt;/code&gt; → &lt;code&gt;performClick&lt;/code&gt; → immediately clear &lt;code&gt;menu&lt;/code&gt;," but clearing synchronously fights AppKit's menu-tracking loop, and the menu flashes and disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Solution]&lt;/strong&gt;&lt;br&gt;
Pop the menu up directly, fully bypassing the assign-and-clear of &lt;code&gt;statusItem.menu&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;statusButtonClicked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentEvent&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;togglePopover&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rightMouseUp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Pop up directly; don't assign then synchronously clear statusItem.menu&lt;/span&gt;
        &lt;span class="c1"&gt;// (it races AppKit's menu-tracking loop)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statusItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;NSMenu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popUpContextMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeMenu&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;button&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;togglePopover&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;Finally, adding &lt;code&gt;LSUIElement = true&lt;/code&gt; to the &lt;code&gt;Info.plist&lt;/code&gt; produced by &lt;code&gt;build-app.sh&lt;/code&gt; makes the Dock icon disappear, and MemeFinder officially becomes a pure menu-bar tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall #5: The settings form is blank — one symptom, three layers of cause
&lt;/h3&gt;

&lt;p&gt;After moving to the menu-bar version, a user reported "the settings window is completely blank." This seemingly simple bug, peeled apart, actually had three layers, each highly representative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: a &lt;code&gt;Form&lt;/code&gt; collapses to zero height inside a hand-rolled &lt;code&gt;NSWindow&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
Originally the settings screen lived in SwiftUI's native &lt;code&gt;Settings { }&lt;/code&gt; scene, which sizes it sensibly. After the refactor it was hosted in a hand-rolled &lt;code&gt;NSWindow(contentViewController: NSHostingController(rootView: SettingsView()))&lt;/code&gt;, and &lt;code&gt;SettingsView&lt;/code&gt; ended with only &lt;code&gt;.frame(width: 460)&lt;/code&gt; — &lt;strong&gt;width only, no height&lt;/strong&gt;. &lt;code&gt;NSWindow(contentViewController:)&lt;/code&gt; sizes the window from the content's natural size, but a SwiftUI &lt;code&gt;Form&lt;/code&gt; is vertically greedy; with no constraint, its natural height resolves to nearly 0, so the window opens as a 460-wide, near-zero-height blank strip. The fix is just to add a height:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&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="c1"&gt;// When hosted in a hand-rolled NSWindow (not a SwiftUI Settings scene), a Form&lt;/span&gt;
&lt;span class="c1"&gt;// with no height constraint collapses to ~0, turning the window into a blank strip.&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;460&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Layer 2: ⌘, and the menu-bar "Settings…" go down two different paths.&lt;/strong&gt;&lt;br&gt;
After adding the height, the user said "still blank." On follow-up I found out he was summoning settings with &lt;strong&gt;⌘,&lt;/strong&gt;, while the menu-bar right-click "Settings…" went down a different path. The reason: ⌘, in a SwiftUI app triggers the &lt;code&gt;Settings { }&lt;/code&gt; scene, and to dodge a state-sharing problem during the refactor, I had set that to &lt;code&gt;Settings { EmptyView() }&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// During the refactor, the Settings scene was left empty to avoid state-sharing&lt;/span&gt;
&lt;span class="c1"&gt;// — so ⌘, opens a blank window&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;EmptyView&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;In other words, &lt;strong&gt;settings had two entry points pointing at different things&lt;/strong&gt;: ⌘, pointed at the empty scene, the menu-bar "Settings…" pointed at the real window. The fix unifies the two paths — let the &lt;code&gt;Settings&lt;/code&gt; scene host the real &lt;code&gt;SettingsView&lt;/code&gt; (so ⌘, works directly), and make the menu-bar "Settings…" open the same native settings window too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;SettingsView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appDelegate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;indexing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;appDelegate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="nv"&gt;onReindex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;appDelegate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reindexNow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                 &lt;span class="nv"&gt;onCancel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;appDelegate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancelReindex&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The menu-bar "Settings…" now opens the same Settings scene&lt;/span&gt;
&lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;openSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;NSApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ignoringOtherApps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;NSApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"showSettingsWindow:"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&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 also leverages the fact that a SwiftUI App body is &lt;code&gt;@MainActor&lt;/code&gt;-isolated — so reading the &lt;code&gt;@MainActor&lt;/code&gt; &lt;code&gt;appDelegate.settings&lt;/code&gt; directly from the body is legal, with no extra bridging needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 (the most insidious): &lt;code&gt;open&lt;/code&gt; doesn't reload a menu-bar app at all.&lt;/strong&gt;&lt;br&gt;
The biggest time-waster in the process was that after recompiling, I'd ask the user to &lt;code&gt;open MemeFinder.app&lt;/code&gt;, yet he kept seeing the old behavior. Because MemeFinder is an &lt;code&gt;LSUIElement&lt;/code&gt; menu-bar-resident app — when an instance is already running, &lt;code&gt;open&lt;/code&gt; only &lt;strong&gt;wakes the existing old process&lt;/strong&gt; instead of relaunching with the new binary. So we were actually testing the same old build the whole time. The correct dev loop is to truly kill it first, then run from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;killall MemeFinderApp 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; swift run MemeFinderApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This layer reminds me: &lt;strong&gt;when debugging, first confirm "what you're testing really is the version you changed"&lt;/strong&gt; — otherwise all your reasoning is built on faulty observations.&lt;/p&gt;




&lt;h1&gt;
  
  
  On the "Development Process" Itself
&lt;/h1&gt;

&lt;p&gt;This project was driven almost entirely by an AI agent workflow of &lt;strong&gt;spec → plan → subagent task-by-task implementation → two-stage review&lt;/strong&gt;: each feature started with a design spec, was broken into independently testable small tasks, every task wrote a failing test first (TDD) before implementing, and after completion an independent review agent checked spec compliance and code quality, followed by one final whole-branch review.&lt;/p&gt;

&lt;p&gt;Several of the pitfalls — &lt;code&gt;GeminiError error 0&lt;/code&gt;, the library/executable split, swallowing &lt;code&gt;CancellationError&lt;/code&gt; during backoff, the menu timing race — were in fact caught half the time during the &lt;strong&gt;review stage&lt;/strong&gt;, not written correctly on the first pass. This echoes that old principle: &lt;strong&gt;having tests as armor, and someone (or an agent) seriously reading the diff, matters far more than writing fast.&lt;/strong&gt; The final project maintains 47 unit tests and a zero-warning release build.&lt;/p&gt;




&lt;h1&gt;
  
  
  Results and Benefits
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Type to find, click to paste&lt;/strong&gt;: type a Chinese description in the menu-bar popover, semantic search instantly lists relevant memes, click one to copy it to the clipboard and paste straight into LINE / Slack / Messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-friendly, searchable offline&lt;/strong&gt;: images and the index live locally (&lt;code&gt;~/Library/Application Support/MemeFinder/index.json&lt;/code&gt;); only the "build the index" step calls Gemini.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A truly handy tool&lt;/strong&gt;: ⌃⌘M is available anytime, menu-bar resident, no Dock footprint; incremental indexing only processes new/changed images, and indexing can show progress and be canceled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A clean, maintainable architecture&lt;/strong&gt;: a two-layer library/executable design, Gemini hidden behind a protocol, pure logic fully covered by tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the development code for this project is open-sourced on GitHub: &lt;a href="https://github.com/kkdai/meme-finder-app" rel="noopener noreferrer"&gt;kkdai/meme-finder-app&lt;/a&gt;. Feel free to clone it, point it at your own meme-collection folder, and experience the joy of "type to find your meme"!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>gemini</category>
      <category>python</category>
    </item>
    <item>
      <title>MTP Speculative Decoding with the 12B Gemma 4 QAT Model on NVIDIA L4, Cloud Run, MCP, and…</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Sun, 21 Jun 2026 14:34:08 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/mtp-speculative-decoding-with-the-12b-gemma-4-qat-model-on-nvidia-l4-cloud-run-mcp-and-18b0</link>
      <guid>https://dev.clauneck.workers.dev/gde/mtp-speculative-decoding-with-the-12b-gemma-4-qat-model-on-nvidia-l4-cloud-run-mcp-and-18b0</guid>
      <description>&lt;h3&gt;
  
  
  MTP Speculative Decoding with the 12B Gemma 4 QAT Model on NVIDIA L4, Cloud Run, MCP, and Antigravity CLI
&lt;/h3&gt;

&lt;p&gt;This article provides a step by step deployment guide for Gemma 4 to a Google Cloud Run hosted GPU enabled system. The latest 12B model is used with QAT formats and deployed with MTP Speculative Decoding. A suite of Python MCP tools is built to simplify management of the vLLM hosted Gemma 4 deployment with Antigravity CLI.&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%2Frxre5zosrew549ld0owi.jpeg" 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%2Frxre5zosrew549ld0owi.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is this project trying to Do?
&lt;/h4&gt;

&lt;p&gt;This project is a DevOps/SRE assistant that uses a Gemma 4 model hosted on Cloud Run with GPU. It provides tools to provision the Docker container and deploy the model, as well as for observability and performance testing.&lt;/p&gt;

&lt;p&gt;This project is similar to a previous project that targeted GPU hosted Gemma4 instances on GCP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/gemma-sre-self-hosted-vllm-infrastructure-agent-afda000c0943" rel="noopener noreferrer"&gt;Gemma-SRE: Self-Hosted vLLM Infrastructure Agent&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Installation Instructions
&lt;/h4&gt;

&lt;p&gt;The detailed installation instructions for Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Python MCP Documentation
&lt;/h4&gt;

&lt;p&gt;The official GitHub Repo provides samples and documentation for getting started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;GitHub - modelcontextprotocol/python-sdk: The official Python SDK for Model Context Protocol servers and clients&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Where do I start?
&lt;/h4&gt;

&lt;p&gt;The strategy for starting MCP development for model management is a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;First, the basic development environment is setup with the required system variables, and a working Antigravity CLI configuration.&lt;/p&gt;

&lt;p&gt;Then, a minimal Python MCP Server is built with stdio transport. This server is validated with Antigravity CLI in the local environment.&lt;/p&gt;

&lt;p&gt;This setup validates the connection from Antigravity CLI to the local server via MCP. The MCP client (Antigravity CLI) and the Python MCP server both run in the same local environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup the Basic Environment
&lt;/h4&gt;

&lt;p&gt;At this point you should have a working Python environment and a working Antigravity CLI installation. The next step is to clone the GitHub samples repository with support scripts:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/xbill9/gemma4-tips
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;init.sh&lt;/strong&gt; from the cloned directory.&lt;/p&gt;

&lt;p&gt;The script will attempt to determine your shell environment and set the correct variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpu-12B-qat-mtp-L4-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session times out or you need to re-authenticate- you can run the &lt;strong&gt;set_env.sh&lt;/strong&gt; script to reset your environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpu-12B-qat-mtp-L4-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Management Tool with MCP Stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the standard MCP libraries provide is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;The connection over stdio will look similar to this:&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;# Initialize FastMCP server
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Self-Hosted vLLM DevOps Agent&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;h4&gt;
  
  
  Running the Python Code
&lt;/h4&gt;

&lt;p&gt;First- switch the directory with the Python version of the MCP sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/gemma4-tips/gpu-12B-qat-mtp-L4-devops-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the release version on the local system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nb"&gt;install
&lt;/span&gt;Processing ./.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project can also be linted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/gpu-12B-qat-mtp-L4-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make lint
ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
All checks passed!
ruff format &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
6 files already formatted
mypy &lt;span class="nb"&gt;.&lt;/span&gt;
Success: no issues found &lt;span class="k"&gt;in &lt;/span&gt;6 &lt;span class="nb"&gt;source &lt;/span&gt;files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting Started with Gemma 4 on GPU
&lt;/h4&gt;

&lt;p&gt;The Official vLLM repo also has Gemma4 specific information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm/releases" rel="noopener noreferrer"&gt;Releases · vllm-project/vllm&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Gemma 12B
&lt;/h4&gt;

&lt;p&gt;Gemma 12B is the latest model in the Gemma4 series. More details are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/introducing-gemma-4-12b/" rel="noopener noreferrer"&gt;Introducing Gemma 4 12B: a unified, encoder-free multimodal model&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.googleblog.com/gemma-4-12b-the-developer-guide/" rel="noopener noreferrer"&gt;Gemma 4 12B: The Developer Guide&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  NVIDIA L4 on Cloud Run
&lt;/h4&gt;

&lt;p&gt;The L4 on Google Cloud Run provides a cost-effective “scale to zero” option for providing inference:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.nvidia.com/blog/google-cloud-run-adds-support-for-nvidia-l4-gpus-nvidia-nim-and-serverless-ai-inference-deployments-at-scale/" rel="noopener noreferrer"&gt;Google Cloud Run Adds Support for NVIDIA L4 GPUs, NVIDIA NIM, and Serverless AI Inference Deployments at Scale | NVIDIA Technical Blog&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Quantization-Aware Training (QAT)
&lt;/h4&gt;

&lt;p&gt;One of the key issues with deploying models is available GPU memory based in the hardware footprint. Along with the new 12B model release- new versions of the model that use QAT to reduce the memory requirements have been released:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/quantization-aware-training-gemma-4/" rel="noopener noreferrer"&gt;Gemma 4 QAT models: Optimizing model compression for mobile and laptop efficiency&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  QAT Model Versions
&lt;/h4&gt;

&lt;p&gt;What are the differences between the versions on Hugging Face?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what is the difference between google/gemma-4-12B-it-qat-q4_0-unquantized and https://huggingface.co/google/gemma-
  4-12B-it-qat-q4_0-gguf and google/gemma-4-12B-it-qat-w4a16-ct

▸ Thought for 1s, 602 tokens
  Analyzing Gemma QAT Variations

● Read(/home/xbill/gemma4-tips/gpu-12B-qat-L4-devops-agent/GEMINI.md) (ctrl+o to expand)

  Here is a comparison of the three Gemma 4 QAT (Quantization-Aware Training) model configurations:

  ### 1. google/gemma-4-12B-it-qat-q4_0-unquantized 

  • Format: Unquantized, high-precision weights (usually float16 or bfloat16) extracted directly from the QAT
  training pipeline.
  • Target Engine: Standard deep learning/fine-tuning framework runtimes (e.g., PyTorch, Transformers, Jax).
  • Primary Use Case:
      • Serving as a high-precision reference baseline.
      • Custom downstream quantization, compilation, or MLX conversion.
      • Research, fine-tuning, or running Speculative Decoding (where this serves as the primary model alongside
      its matching MTP assistant/draft model).


  ### 2. google/gemma-4-12B-it-qat-q4_0-gguf 

  • Format: GGUF (specifically quantized using the standard q4_0 4-bit method).
  • Target Engine: Local CPU, Apple Silicon, or consumer GPU engines (e.g., llama.cpp , Ollama, LM Studio).
  • Primary Use Case:
      • Zero-setup local execution with minimal memory requirements.
      • Running LLMs efficiently on consumer-grade hardware.


  ### 3. google/gemma-4-12B-it-qat-w4a16-ct 

  • Format: Compressed Tensors format ( -w4a16-ct ), which stores 4-bit weights while performing computation using
  16-bit activations.
  • Target Engine: High-performance, high-throughput cloud inference engines (specifically vLLM or SGLang ).
  • Primary Use Case:
      • High-concurrency cloud deployments utilizing GPU accelerators (e.g., NVIDIA L4).
      • Optimizing GPU memory usage and improving inference speed while maintaining near-lossless model accuracy
      via QAT.

  ──────
  ### 📊 Summary Reference Table

   Checkpoint Configuration | Serialization Format | Target Platform | Primary Engine
  ---------------------------|---------------------------|----------------------------|----------------------------
    ...-qat-q4_0-unquantized | Unquantized ( BF16 / FP16 | Custom compilation / | Transformers / Speculative
                             | ) | research | Drafts
    ...-qat-q4_0-gguf | GGUF (4-bit weight) | Local Desktop/Server | llama.cpp / LM Studio /
                             | | (CPU/GPU) | Ollama
    ...-qat-w4a16-ct | compressed-tensors (4- | Cloud GPU Deployments | vLLM / SGLang 
                             | bit/16-bit) | |

  For more details on QAT architecture and configuration, see GEMINI.md.
  ──────
  ### Summary of Work

  • Reviewed Context: Analyzed the workspace documentation GEMINI.md to compare the unquantized, GGUF, and
  compressed-tensors configurations.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More documentation is available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ai.google.dev/gemma/docs/core#qat" rel="noopener noreferrer"&gt;Gemma 4 model overview | Google AI for Developers&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  MCP stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the MCP protocol provides is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;In this project Antigravity CLI is used as the MCP client to interact with the Python MCP server code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI mcp_config.json
&lt;/h4&gt;

&lt;p&gt;A sample MCP server file is provided in the .agents directory:&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gpu-devops-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/home/xbill/gemma4-tips/gpu-12B-qat-mtp-L4-devops-agent/server.py"&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;"env"&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;"GOOGLE_CLOUD_PROJECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aisprint-491218"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_CLOUD_LOCATION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"VLLM_BASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gpu-12b-qat-mtp-289270257791.us-east4.run.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MODEL_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/models/gemma-4-12B-it-qat-w4a16-ct"&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;h4&gt;
  
  
  Validation with Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;The final connection test uses Antigravity CLI as a MCP client with the Python code providing the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
&amp;gt; ✓ google-dev-knowledge Tools: search_documents, answer_query, get_documents
   ✓ gpu-devops-agent Tools: save_hf_token, get_vllm_endpoint, list_vertex_models, list_bucket_models,
                       analyze_cloud_logging, +21 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Checking System status
&lt;/h4&gt;

&lt;p&gt;The status can be checked with an MCP tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; vllm_status

● gpu-devops-agent/status_vllm(Checking status of the vLLM deployment via MCP) (ctrl+o to expand)

  Here is the current status of the gpu-12b-qat-mtp Cloud Run deployment:

  • Service Status: 🟢 Ready (all configurations and routing conditions are met)
  • Latest Revision: gpu-12b-qat-mtp-00032-nwb (deployed on 2026-06-21)
  • Service URL: https://gpu-12b-qat-mtp-wgcq55zbfq-uk.a.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross Check The Deployed Model
&lt;/h4&gt;

&lt;p&gt;Once the model starts locally- the MCP tool allows for model verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; verify_model_health

● gpu-devops-agent/verify_model_health(Verifying model health for gpu-12b-qat-mtp via MCP) (ctrl+o to expand)

  The model health check has passed successfully:

  • Model: /mnt/models/gemma-4-12B-it-qat-w4a16-ct 
  • Response: "Yes, I am working! I am ready to help you with any..." 
  • Latency: 0.88 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; get_active_models

131.0k in | 677 out | 415 thinking
● Bash(ENDPOINT="https://gpu-12b-qat-mtp-wgcq55zbfq-uk.a.run.app"; TOKEN=$(gcloud auth pri...) (ctrl+o to
expand)

  The active model on the deployment is:

  • Model ID: /mnt/models/gemma-4-12B-it-qat-w4a16-ct 
  • Owned by: vllm 

  (Note: The /health check returned a 503 in get_model_details because the request was
  unauthenticated, and Cloud Run requires an IAM identity token for routing. When queried with the proper
  authorization headers, the endpoint returns a clean 200 OK health status).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is the current system status:

  ### 🌀 GPU Cloud Run System Status ( get_system_status )

  • vLLM Health: 🟢 Online ( [https://gpu-12b-qat-l4-devops-agent-289270257791.us-east4.run.app](https://gpu-12b-qat-l4-devops-agent-289270257791.us-east4.run.app) )
  • Cloud Run Service Status: 🟢 Ready
  ──────
  ### 🧩 Active Model Details ( get_model_details )

  • Active Model: /mnt/models/gemma-4-12B-it-qat-w4a16-ct 
  • Health Endpoint: Healthy ✅
  ──────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Review the Model
&lt;/h4&gt;

&lt;p&gt;Check the model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the model details including vllm optimization

  Here are the model and vLLM optimization details for the active gpu-12b-qat-mtp deployment:

  ### 🧩 Core Model Details

  • Primary Model: google/gemma-4-12B-it-qat-w4a16-ct (Quantization-Aware Training, INT4 weights with 16-
  bit activations)
  • Speculative/Assistant Model (MTP): google/gemma-4-12B-it-assistant (Multi-Token Prediction)
  • Speculative Tokens: 3 ( --spec-tokens=3 , --spec-method=mtp )
  ──────
  ### 🚀 vLLM Performance Optimizations

   Category | Optimization Parameter | Value/Setting | Purpose
  ---------------------|---------------------------------|---------------|----------------------------------
   Inference Kernel | VLLM_DISABLE_FLASHINFER | 0 (Enabled) | Uses high-performance FlashInfer
                       | | | kernels for attention
                       | | | operations.
   Sampler | VLLM_USE_FLASHINFER_SAMPLER | 1 (Enabled) | Enables FlashInfer's optimized
                       | | | sampling kernels, decreasing
                       | | | time-to-first-token.
   Memory Allocation | PYTORCH_CUDA_ALLOC_CONF | expandable_se| Minimizes VRAM memory
                       | | gments:True | fragmentation and helps prevent
                       | | | Out-Of-Memory (OOM) errors.
   KV Cache | --kv-cache-dtype | fp8 | Quantizes key-value cache to 8-
                       | | | bit precision, doubling the
                       | | | available context capacity.
   KV Cache Allocation | --gpu-memory-utilization | 0.95 | Allocates 95% of GPU VRAM to
                       | | | vLLM's storage caches.
   Batching &amp;amp; Context | --max-num-batched-tokens | 8192 | Sets maximum batched prefill
                       | | | tokens to allow higher
                       | | | throughput under concurrency.
   Chunking | --enable-chunked-prefill | Enabled | Chunks massive requests to avoid
                       | | (Implicit) | stalling shorter requests in
                       | | | queue.
   Load Strategy | --safetensors-load-strategy | prefetch | Prefetches model weights during
                       | | | container boot-up to reduce
                       | | | startup time.
   Scheduling | --async-scheduling | Enabled | Enables asynchronous scheduling
                       | | | loop in vLLM to pipeline
                       | | | execution.
   Context Limit | --max-model-len | 32768 | Limits maximum context length to
                       | | | keep memory reservation stable.
   Concurrency Limit | --max-num-seqs | 8 | Caps maximum concurrent request
                       | | | sequences active in a forward
                       | | | pass.
   Threading | MKL_NUM_THREADS , | 1 | Forces single-threading on CPU
                       | OMP_NUM_THREADS | | to avoid CPU core contention on
                       | | | the host.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and check the metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Here is a summary of the key metrics retrieved from the active gpu-12b-qat-mtp vLLM engine:

  ### 🚀 Performance &amp;amp; Throughput

  • Prefill Tokens Processed: 29,389,524 tokens (prefilled)
  • Generation Tokens Processed: 9,494 tokens (generated)
  • Active Running Requests: 0.0 (Idle)
  • Active Waiting Requests: 0.0 
  • KV-Cache Usage: 0.0% (Idle)

  ### 🔮 Speculative Decoding / MTP Statistics

  • Total Draft Iterations: 139 
  • Total Speculative/Draft Tokens Proposed: 417 tokens
  • Total Speculative Tokens Accepted: 23 tokens
  • Speculative Acceptance by Position:
      • Position 0: 11 tokens accepted
      • Position 1: 6 tokens accepted
      • Position 2: 6 tokens accepted
      • Overall Acceptance Rate: ~5.5% (demonstrating why MTP is heavily bound by scheduling and sequential
      evaluation overhead on a single GPU for typical workloads)

  ### 🕒 Latency &amp;amp; Queuing (Cumulative)

  • Average Time to First Token (TTFT): ~0.28 seconds
  • Average Request Queue Time: ~0.016 milliseconds (minimal queue delay)
  • Average End-to-End Latency: ~0.28 seconds (highly responsive)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is online — you can send queries to the Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● gpu-devops-agent/query_gemma4_with_stats(Querying Gemma 4 via MCP with stats returned) (ctrl+o to expand)

  Here are the performance stats and the model response to your query:

  ### 📊 Performance Statistics

  • Active Model: /mnt/models/gemma-4-12B-it-qat-w4a16-ct 
  • Time to First Token (TTFT): 0.215 seconds
  • Total Generation Time: 12.60 seconds
  • Generation Speed: 18.16 tokens/s
  • Total Generated Tokens: ~225 tokens
  ──────
  ### 💬 Model Response

────────────────────────────────────────────────────────────
  Depending on the context, Gemma usually refers to one of two things: Google's family of open AI models, or
  a gemstone.

  #### 1. In Artificial Intelligence (Google’s AI Models)

  Gemma is a family of lightweight, state-of-the-art open models developed by Google DeepMind.

  It is built using the same research and technology as Google's larger Gemini models, but is designed to be
  small and efficient enough for developers to run on their own hardware (such as laptops or single-GPU
  servers).

  • Open Weights: The model weights are publicly available, allowing developers to fine-tune, download, and
  host them privately.
  • Sizes: Available in various sizes (e.g., 2B, 7B, 9B, 27B parameters, and the latest 12B/31B iterations
  in Gemma 4) to balance capability and hardware constraints.
  • Versatility: Capable of text generation, summarization, coding, and logical reasoning.

  #### 2. In Gemology &amp;amp; Names

  • Gemstone: "Gemma" is the Latin word for a precious gemstone or jewel.
  • Name: A popular feminine given name in several European languages (especially Italian), translating to
  "precious stone" or "gem."

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benchmark the Local Model
&lt;/h4&gt;

&lt;p&gt;The local model was benchmarked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The benchmark sweep has completed successfully. Here are the compiled results across all combinations of context
  window sizes and concurrency levels:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The analysis is reviewed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; summarize benchmark results

&amp;gt; summarize the benchmark results
  Here is a summary of the benchmark results comparing the Standard QAT (No MTP) configuration against
  various Multi-Token Prediction (MTP) settings on a single NVIDIA L4 GPU:
  ──────
  ### 1. Standard Workloads (Short Contexts, 128 Tokens)

  For typical developer and agent workloads, MTP/Speculative Decoding degrades performance.

  • Why? The sequential overhead of running the draft model ( google/gemma-4-12B-it-assistant ) on a single
  GPU offsets the memory bandwidth savings when the primary model is already highly optimized.
  • Optimized MTP-3 Impact: By enabling FlashInfer and increasing max-num-batched-tokens to 8192 , we
  recovered significant throughput under high concurrency. At 512 concurrent users, throughput for MTP-3
  improved from 8.4 req/s to 12.84 req/s (a +52.8% increase).

  Throughput Comparison (Requests/sec at 128 context):

   Concurrency (Users) | Standard QAT (No MTP) | MTP-4 | MTP-3 | MTP-2 | Optimized MTP-3 (Tuned)
  ---------------------|-----------------------|-----------|-----------|----------|-------------------------
            8 | 18.9 | 11.3 | 8.8 | 8.0 | 8.53
           64 | 23.8 | 12.9 | 11.9 | 11.9 | 12.12
           512 | 23.7 | 13.0 | 8.4 | 8.0 | 12.84
  ──────
  ### 2. Large Context Workloads (16,384 Tokens)

  At very large context lengths, inference becomes heavily memory-bandwidth bound due to the massive size of
  the KV cache. Here, MTP improves performance.

  • Why? Speculative decoding allows the GPU to evaluate multiple tokens per target model forward pass,
  reducing the frequency of loading the giant KV cache from High Bandwidth Memory (HBM) to GPU SRAM.
  • Best Configuration: MTP-3 (3 tokens) and MTP-4 yield the highest overall throughputs. Under high
  concurrency (512 users), MTP-3 outperforms Standard QAT by +11.4%. Under extreme load (2048 users),
  Optimized MTP-3 maintains a stable 6.66 req/s throughput.

  Throughput Comparison (Requests/sec at 16K context):

   Concurrency (Users) | Standard QAT (No MTP) | MTP-4 | MTP-3 | MTP-2 | Optimized MTP-3 (Tuned)
  ---------------------|-----------------------|-----------|-----------|----------|-------------------------
            8 | 5.16 | 6.55 | 0.15 | 0.17 | 0.14
           64 | 7.39 | 8.11 | 7.65 | 8.00 | 7.61
           512 | 7.54 | 8.24 | 8.40 | 7.70 | 8.13
          2048 | 6.30 | 8.00 | 7.55 | 8.01 | 6.66
  ──────
  ### 💡 Core Recommendations

  1. Production DevOps Agent (Default): Keep MTP Disabled. The standard QAT model ( google/gemma-4-12B-it-
  qat-w4a16-ct ) alone provides the highest throughput (up to 23.8 req/s) and lowest latencies for standard
  agent interactions.
  2. Long-Context Tasks (16K+): Enable MTP-3 or MTP-4 with FlashInfer and batched token tuning enabled to
  accelerate processing times and improve throughput by up to 27%.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, What was that? I thought speculative decoding improved performance!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; why didn't MTP improve performance

  Multi-Token Prediction (MTP) / Speculative Decoding failed to improve performance for standard workloads
  (short context sizes) due to three key technical bottlenecks on the single NVIDIA L4 GPU configuration:

  ### 1. Compute-Bound vs. Memory-Bound Regimes

  • When MTP Helps (Memory-Bound): LLM decoding is typically memory-bandwidth bound because the GPU has to
  fetch the entire model's weights from High Bandwidth Memory (HBM) to SRAM for every single token generated.
  Speculative decoding speeds this up by using a tiny draft model to propose tokens, allowing the main model
  to verify them in one joint forward pass.
  • When MTP Fails (Compute-Bound): When you have multiple concurrent users, vLLM groups these requests into
  batches. As the batch size increases, the workload shifts from memory-bound to compute-bound (fully
  utilizing the Tensor Cores). In this regime, weight loading is no longer the bottleneck; instead, raw
  compute capability is. Running the draft model sequentially to generate speculative tokens adds extra
  compute steps to the queue, increasing latency and reducing throughput.

  ### 2. Draft Model Execution &amp;amp; Sequential Verification Overhead

  • Speculative decoding requires the GPU to run the assistant model sequentially (e.g., 3 draft steps)
  before running the target model once to verify them.
  • The Gemma 4 12B QAT model is already extremely fast natively on the L4 GPU. Because of this, the
  overhead of running the draft model sequentially is disproportionately high compared to the target model.
  If even a few proposed tokens are rejected, the wasted draft passes result in a net-negative speedup.

  ### 3. VRAM Contention and Scheduler Limits

  • Assistant Model Overhead: Loading the speculative draft model ( google/gemma-4-12B-it-assistant )
  consumes about 0.8 GB of VRAM. While small, this reduces the space available for vLLM's KV cache.
  • Token Budget Contention: Speculative decoding requires reservation of extra token slots in the scheduler.
  As vLLM warns, this limits the maximum concurrent sequences the engine can schedule per step, causing
  requests to queue up and increasing time-in-queue under high concurrency.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what are the bottlenecks?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the key constraints for the deployment on this hardware profile

  Deploying serving stacks on this specific hardware profile—a single NVIDIA L4 GPU (24GB VRAM) combined
  with Cloud Run Gen2 (4 vCPUs, 16GB System RAM)—introduces several critical constraints:

  ### 1. GPU VRAM Limitations (24 GB)

  • Model Footprint vs. KV Cache: The L4's 24GB VRAM is the primary ceiling. The baseline Gemma 4 12B model
  in half-precision (bfloat16) consumes ~24GB of memory, leaving 0 GB for the KV cache and causing immediate
  Out-of-Memory (OOM) crashes under concurrent load.
  • The QAT Requirement: To serve the model, we must use the Quantization-Aware Training (QAT) 4-bit
  compressed format ( w4a16-ct ). This compresses the model weights to ~6 GB, freeing up ~18 GB for the KV
  cache.
  • Speculative Model Tax: Enabling MTP adds the assistant model weights to VRAM (~0.8 GB), further
  squeezing the KV cache space.

  ### 2. GPU Memory Bandwidth (300 GB/s)

  • Compared to H100 (3.35 TB/s) or A100 (2.0 TB/s), the L4 has relatively low memory bandwidth (300 GB/s).
  This makes single-user, memory-bound autoregressive decoding natively slower, which is why optimizing the
  kernels (FlashInfer) and memory alignment is critical.

  ### 3. Cloud Run CPU Throttling ( --no-cpu-throttling )

  • Cloud Run Gen2 defaults to throttling CPU down to near-zero when no requests are actively being
  processed. For GPU serving, this must be disabled ( --no-cpu-throttling ). If CPU throttling is active,
  the container's CPU cannot coordinate with the GPU quickly enough, resulting in severe timeouts, failed
  startup health probes, and model-loading hangs.

  ### 4. GCS FUSE UID/GID Permissions

  • Model weights are mounted from Google Cloud Storage (GCS) using Cloud Run's FUSE volume mount.
  • The official vllm/vllm-openai container runs as the non-root user vllm (UID/GID 1001 ). If you do
  not explicitly mount the GCS volume with the options mount-options=uid=1001;gid=1001 , the container will
  fail to boot with a Permission Denied error because the non-root user cannot read the mounted model
  files.

  ### 5. System RAM (16 GB) and vCPUs (4)

  • The host system only has 16 GB of System RAM and 4 vCPUs. Loading model weights from the GCS mount into
  the GPU requires buffering through system memory. During boot, vLLM's initialization and model loading
  must be carefully managed (e.g. using safetensors-load-strategy=prefetch ) to prevent exceeding the
  host's 16GB memory ceiling, which triggers immediate container evictions (OOMKilled).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cost Analysis
&lt;/h4&gt;

&lt;p&gt;OK so there are some limitations with this hardware profile. What about the cost benefits?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; how cost effective is this deployment?

  This deployment profile (Cloud Run Gen2 GPU + NVIDIA L4) is exceptionally cost-effective for SRE/DevOps
  automation workloads, primarily due to its serverless architecture, hardware efficiency, and the optimized
  model format.
  Here is a breakdown of the cost dynamics:
  ### 1. Serverless Scale-to-Zero Advantage

  Unlike traditional setups on VMs (Compute Engine) or Kubernetes (GKE) where you pay for the GPU 24/7 even
  when idle, Cloud Run allows you to configure min-instances=0 .

  • Idle Cost: $0 when no SRE alerts or queries are active.
  • Cold Start Consideration: While scaling to zero saves maximum budget, it incurs a cold start penalty
  (~1–2 minutes to mount GCS weights and boot vLLM). For critical SRE workloads, keeping min-instances=1 
  is recommended, which costs a fixed baseline but is still cheaper than full VM hosting due to Cloud Run's
  granular allocation.

  ### 2. High Performance-per-Dollar of the L4 GPU

  The NVIDIA L4 (based on the Ada Lovelace architecture) is designed specifically for low-cost, energy-
  efficient inference:

  • Cloud Run GPU Pricing: Estimated at roughly $0.40 to $0.50 per hour for the GPU resource alone, plus
  minimal CPU/Memory allocation.
  • Total Hourly Cost (Active): Approximately $0.60 to $0.70/hour when running.
  • Comparison to A100/H100: An A100 instance generally costs $2.00 to $3.50/hour. Since the optimized Gemma
  4 12B QAT model achieves a highly respectable ~24 req/s on a single L4, you get a massive percentage of
  A100-level throughput at a fraction of the hardware cost.

  ### 3. Efficiency Gains from Quantization (QAT)

  Using the QAT INT4 model ( w4a16-ct ) directly improves cost-effectiveness:

  • Memory Reduction: Compressing the weights from 24GB to 6GB allows the model to fit comfortably on a
  single 24GB L4 GPU.
  • Avoiding Multi-GPU Costs: Without quantization, serving a 12B model in bfloat16 would require either a
  much more expensive GPU (like an A100 40GB/80GB) or multiple L4 GPUs in a tensor-parallel setup, which
  would double or triple the hourly cost.

  ### 4. Summary Financial Comparison

   Metric | Dedicated VM (e.g., g2-… | Cloud Run (Scale-to-Zero… | Cloud Run (Always-On, mi…
  ----------------------|--------------------------|---------------------------|----------------------------
   Hourly Rate (Active) | ~$0.70 | ~$0.70 | ~$0.70
   Monthly Cost | ~$504.00 (fixed) | ~$42.00 (usage-based) | ~$504.00 (max baseline)
   Best Fit | Continuous 24/7 high- | Intermittent SRE alert | Real-time SRE response,
                        | throughput workloads | analysis, dev/testing | zero cold-start tolerance

───────────────────────────────────────────────────────────────────────────────────────────────────────────────
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One last cross-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; one final review- MTP or not with this deployment? and what about QAT?

  • The Only Exception: If your agent is routinely processing massive datasets or long-context documents
  (16K+ tokens in a single request), MTP will provide a 10% to 27% speedup. Otherwise, the overhead is not
  worth the trade-off.
  ──────
  ### 2. Quantization-Aware Training (QAT): YES (Mandatory)

  Using the QAT compressed weights ( w4a16-ct ) is absolutely critical and mandatory for this hardware
  profile.

  • Why? A single NVIDIA L4 GPU has only 24GB of VRAM.
      • If you deploy the standard bfloat16 model, the weights consume almost all 24GB, leaving 0 GB for the
      KV cache. The service will crash with Out-of-Memory (OOM) errors as soon as multiple requests arrive.
      • The QAT INT4 model compresses the weights to ~6 GB, leaving ~18 GB of VRAM entirely for the KV cache.
      This allows the system to easily handle up to 512 concurrent users with a 100% success rate.
  • Zero Quality Loss: Because this is QAT (quantization simulation during training) rather than standard
  post-training quantization, you get the memory savings of a 4-bit model with the logical accuracy and
  reasoning capabilities of the full 16-bit baseline model.

  ### Final Production Stack Recommendation:

  │ Serve google/gemma-4-12B-it-qat-w4a16-ct using vLLM on a single L4 GPU with MTP disabled, and
  │ FlashInfer enabled for optimal throughput (~24 req/s) and maximum concurrent capacity.

&amp;gt; so MTP is bad?

  MTP is not bad in general—it is just wrong for this specific deployment use case.

  Think of MTP as a highly specialized optimization tool. It works exceptionally well in certain situations,
  but acts as a bottleneck in others.

  ### When MTP is Good:

  • Single-User / Local Environments: If you are running a model locally on your laptop (e.g., Apple Silicon
  │ Serve google/gemma-4-12B-it-qat-w4a16-ct using vLLM on a single L4 GPU with MTP disabled, and
  or a desktop GPU) for a single user, the system is strictly memory-bound. Here, MTP significantly
  accelerates token generation (often by 1.5x to 2x) because it reduces the time spent loading model weights
  from memory.
  • Extremely Long Contexts (16K+ tokens): When processing huge logs or long documents, loading the massive
  KV cache dominates the GPU's time. MTP reduces the frequency of these expensive memory transfers,
  resulting in a 10% to 27% throughput improvement even under high concurrency.

  ### When MTP is Bad (This DevOps Agent Profile):

  • High Concurrency (Batched Requests): When multiple users query the DevOps agent at the same time, vLLM
  batches the requests. This shifts the GPU from a memory-bound state to a compute-bound state (where Tensor
  Cores are running at 100%). In this state, the sequential execution of the draft model adds extra compute
  overhead, causing requests to queue up and slowing everything down.
  • Hardware Resource Constraints: On a single 24GB GPU, loading the draft model takes up precious VRAM and
  restricts the scheduling capacity of the main engine, causing throughput to degrade under standard log
  analysis sizes.

  ### Summary

  MTP is a latency optimizer for single-user, memory-bound tasks. It is not a throughput optimizer for
  concurrent, compute-bound backend APIs. Since DevOps/SRE agents are backend APIs that handle concurrent
  workloads, turning MTP off yields better overall performance.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The strategy for using MCP for Gemma 4 GPU deployment with Antigravity CLI was validated with a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;A minimal stdio transport MCP Server was started from Python source code and validated with Antigravity CLI running as a MCP client in the same local environment. This Python server provided all of the management tools to deploy and troubleshoot the speculative decoding options with the Gemma 4 model.&lt;/p&gt;

</description>
      <category>antigravity</category>
      <category>mcps</category>
      <category>speculativedecoding</category>
      <category>qat</category>
    </item>
    <item>
      <title>Chatting with your Data: Conversational Analytics in BigQuery</title>
      <dc:creator>Aryan Irani</dc:creator>
      <pubDate>Sat, 20 Jun 2026 07:45:01 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/chatting-with-your-data-conversational-analytics-in-bigquery-5545</link>
      <guid>https://dev.clauneck.workers.dev/gde/chatting-with-your-data-conversational-analytics-in-bigquery-5545</guid>
      <description>&lt;p&gt;For the last decade, the workflow for Business Intelligence hasn't changed much: A business stakeholder asks a question, a Data Engineer writes the ad-hoc SQL, and a dashboard is built. But as data scales to the petabyte level, this reactive cycle creates massive bottlenecks.&lt;/p&gt;

&lt;p&gt;What if business users could just chat directly with the database?&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://docs.cloud.google.com/bigquery/docs/conversational-analytics" rel="noopener noreferrer"&gt;BigQuery Conversational Analytics&lt;/a&gt;. Google Cloud has effectively turned the traditional data warehouse into an active participant. By leveraging Gemini, Conversational Analytics allows users to query massive datasets using natural language. It understands the intent, generates the complex SQL, and returns the data (or geographic visualizations) instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with "Text-to-SQL" Toys
&lt;/h2&gt;

&lt;p&gt;We've all seen the basic "Text-to-SQL" AI wrappers on Twitter. They look great in a controlled demo, but they fall apart in production. Why? Because raw LLMs don't understand your company's unique business logic. If an AI doesn't know that your definition of "Net Profit" excludes returned items, the data it returns is not just wrong - it's dangerous.&lt;/p&gt;

&lt;p&gt;To deploy AI over enterprise data, you need strict governance.&lt;/p&gt;

&lt;p&gt;You need to be able to explicitly teach the AI your database schema. You need &lt;strong&gt;Dataplex Glossaries&lt;/strong&gt; to lock down business terminology so the AI never guesses a formula. You need &lt;strong&gt;Parameterized Verified Queries&lt;/strong&gt; to ensure highly sensitive financial reports use pre-approved SQL. And crucially, you need &lt;strong&gt;Financial Controls&lt;/strong&gt; (like Maximum Bytes Billed) so a business user asking a vague question doesn't trigger a $5,000 table scan.&lt;/p&gt;

&lt;p&gt;BigQuery Conversational Analytics isn't just an AI wrapper; it's a governed enterprise agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Playlist: Build It&amp;nbsp;Yourself
&lt;/h2&gt;

&lt;p&gt;I spent the last few weeks using this product. To show you exactly how you can get started, I recorded a 4-part vide playlist on YouTube.&lt;/p&gt;

&lt;p&gt;If you are a Data Engineer, Cloud Architect, or BI Analyst, this series will show you exactly how to build and govern your own AI data agents from scratch:&lt;/p&gt;

&lt;p&gt;📺 Part 1: The AI Reasoning Pipeline We dive into the BigQuery Studio UI and test Gemini's ability to perform comparative analysis on the Google Trends public dataset without writing a single line of SQL.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/XZuQNgChh0E"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;📺 Part 2: Building Custom Data Agents An AI can't magically understand your database schema. I show you how to connect your tables and write System Instructions to explicitly control the SQL that Gemini generates.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HN5XqkWyCys"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;📺 Part 3: Enterprise Data Governance We tackle the hardest part of Enterprise AI. I show you how to lock down your agent using Column Metadata, Dataplex Glossaries, and strict financial controls to prevent petabyte-scale billing surprises.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/POWrsGBqsOw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;📺 Part 4: Automating Multi-Table Relational Joins In the grand finale, we use everything we've built to force the agent to write a flawless, massive 3-table relational JOIN from a single natural language prompt.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ZmIPW7k2mYQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The role of the Data Engineer is shifting from writing ad-hoc SQL to governing autonomous data pipelines. If you're building in Google Cloud, I highly recommend getting hands-on with this.&lt;/p&gt;

&lt;p&gt;You can watch the full series here: &lt;a href="https://youtube.com/playlist?list=PL_MCVBMm-9sogdOzjqIcbX-dAizXux91c&amp;amp;si=890s67FrqxYfZTeu" rel="noopener noreferrer"&gt;https://youtube.com/playlist?list=PL_MCVBMm-9sogdOzjqIcbX-dAizXux91c&amp;amp;si=890s67FrqxYfZTeu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to reach out if you have any issues/feedback at &lt;a href="mailto:aryanirani123@gmail.com"&gt;aryanirani123@gmail.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>bigquery</category>
      <category>dataagents</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Skills over System Prompts: Building an Anki Tutor with the Antigravity SDK</title>
      <dc:creator>Ertuğrul Demir</dc:creator>
      <pubDate>Fri, 19 Jun 2026 10:09:03 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk-2o8f</link>
      <guid>https://dev.clauneck.workers.dev/gde/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk-2o8f</guid>
      <description>&lt;p&gt;AI has made me a little lazier.&lt;/p&gt;

&lt;p&gt;Not dramatically lazy. Not "the robots will do everything" lazy. More like: once you get used to asking an agent to do boring work, every small manual workflow starts looking suspicious.&lt;/p&gt;

&lt;p&gt;Anki is a perfect example.&lt;/p&gt;

&lt;p&gt;Anki is great. I use it to remember things I study, subjects I work on, and the weird little decisions hidden inside codebases. Spaced repetition works. The problem is not Anki.&lt;/p&gt;

&lt;p&gt;The problem is me.&lt;/p&gt;

&lt;p&gt;I can already see the rot setting in. On complex cards, my brain starts negotiating with itself. "Yeah, I basically knew that." "Close enough." "I would have remembered it in context." Then I press Good and move on.&lt;/p&gt;

&lt;p&gt;That is not studying. That is self-certified vibes.&lt;/p&gt;

&lt;p&gt;What I actually wanted was a study buddy sitting on top of my real Anki collection. Someone to ask the card, wait for my answer, reveal the real answer, compare it honestly, explain the gap, and only then help decide whether it was Again, Hard, Good, or Easy.&lt;/p&gt;

&lt;p&gt;AI is annoyingly good for that.&lt;/p&gt;

&lt;p&gt;It is also useful when taking over a new project. When I enter a repo, I do not only want a summary. I want to be quizzed later on the key decisions, the architecture, the gotchas, and the "why is it like this?" parts. Anki is great for that too.&lt;/p&gt;

&lt;p&gt;But I am still lazy.&lt;/p&gt;

&lt;p&gt;I am not going to manually write every card. I am not going to keep every deck updated by hand. And if I am studying from my phone, I am definitely not going to type long answers into a chat just so the agent can grade me. Voice needs to work too.&lt;/p&gt;

&lt;p&gt;So the project quickly stopped being "connect Gemini to Anki."&lt;/p&gt;

&lt;p&gt;It became a small agent system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a terminal tutor for focused review sessions&lt;/li&gt;
&lt;li&gt;a Telegram tutor for studying from my phone, including voice answers&lt;/li&gt;
&lt;li&gt;a deck builder that creates cards from web research or a local codebase&lt;/li&gt;
&lt;li&gt;a watch mode that can notice code changes and create cards while I work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a lot of behavior.&lt;/p&gt;

&lt;p&gt;My first instinct was the usual one: write a bigger system prompt. Tell the agent how to run a study session. Tell it how to write good flashcards. Tell it how to inspect a codebase and turn architecture into cards. Tell it how to behave differently in Telegram. Tell it not to touch scheduling unless I approve.&lt;/p&gt;

&lt;p&gt;That works for about ten minutes.&lt;/p&gt;

&lt;p&gt;Then the system prompt becomes a junk drawer.&lt;/p&gt;

&lt;p&gt;The hard part was not giving the agent tools.&lt;/p&gt;

&lt;p&gt;The hard part was giving it habits.&lt;/p&gt;

&lt;p&gt;That is where the Google Antigravity SDK fit really well. It gives you the agent runtime as a Python library: custom tools, reusable skills, lifecycle hooks, safety policies, streaming, triggers, and multiple ways to run the same agent logic from different surfaces.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Antigravity SDK Gives You
&lt;/h2&gt;

&lt;p&gt;The Antigravity SDK is not just a wrapper around a chat model.&lt;/p&gt;

&lt;p&gt;It gives you programmatic access to the same agent runtime behind Google Antigravity 2.0 and the Antigravity CLI, but from Python.&lt;/p&gt;

&lt;p&gt;That matters because a real agent is not only a model call. A real agent needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tools&lt;/li&gt;
&lt;li&gt;memory across turns&lt;/li&gt;
&lt;li&gt;permissions&lt;/li&gt;
&lt;li&gt;hooks&lt;/li&gt;
&lt;li&gt;skills&lt;/li&gt;
&lt;li&gt;streaming&lt;/li&gt;
&lt;li&gt;triggers&lt;/li&gt;
&lt;li&gt;safety around side effects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SDK puts those behind one main abstraction: &lt;code&gt;Agent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The smallest useful version really is tiny:&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;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.antigravity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LocalAgentConfig&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalAgentConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;agent&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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 files are in the current directory?&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;if&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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;google-antigravity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then set a Gemini API key from Google AI Studio:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the hello world.&lt;/p&gt;

&lt;p&gt;The useful version starts when you compose the runtime features around a real workflow.&lt;/p&gt;

&lt;p&gt;In this project, the Antigravity SDK pieces mapped like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Antigravity SDK capability&lt;/th&gt;
&lt;th&gt;Where I used it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Agent&lt;/code&gt; / &lt;code&gt;LocalAgentConfig&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the terminal tutor, Telegram tutor, and deck builder all run on the same agent runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom Python tools&lt;/td&gt;
&lt;td&gt;AnkiConnect actions like &lt;code&gt;get_due_cards&lt;/code&gt;, &lt;code&gt;show_answer&lt;/code&gt;, &lt;code&gt;rate_card&lt;/code&gt;, and &lt;code&gt;add_notes&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills_paths&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;shared &lt;code&gt;review-buddy&lt;/code&gt;, &lt;code&gt;plain-cards&lt;/code&gt;, and &lt;code&gt;codebase-cards&lt;/code&gt; behavior packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle hooks&lt;/td&gt;
&lt;td&gt;sync on session start/end, deck backup before writes, audit log after scheduling changes, tool-error recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety policies&lt;/td&gt;
&lt;td&gt;practice mode blocks &lt;code&gt;rate_card&lt;/code&gt; so cram sessions cannot change real scheduling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;the deck builder prints progress while the agent researches and creates cards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Triggers&lt;/td&gt;
&lt;td&gt;watch mode reacts to &lt;code&gt;.py&lt;/code&gt; file changes and asks the agent to card important changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built-in read-only tools&lt;/td&gt;
&lt;td&gt;codebase mode lets the agent inspect a repo without editing it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That list is the reason this worked better as an SDK project than as one giant prompt around a model call.&lt;/p&gt;

&lt;p&gt;Now, the first useful step: give the agent hands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Giving the Agent Hands: Anki as Python Tools
&lt;/h2&gt;

&lt;p&gt;Anki already has an HTTP API through the AnkiConnect add-on. The entire bridge is basically one POST to localhost:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&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;**&lt;/span&gt;&lt;span class="n"&gt;params&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;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8765&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&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;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&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;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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;From there, the agent tools are just normal Python functions.&lt;/p&gt;

&lt;p&gt;A simplified version:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_decks&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List all Anki decks with their due counts.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;decks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deckNames&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;getDeckStats&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;decks&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats&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;get_due_cards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return due cards without revealing the answer side.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deck:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; is:due&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;deck&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is:due&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;card_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;findCards&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cardsInfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;card_ids&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cards&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;rate_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Submit a user-confirmed Anki rating: 1 Again, 2 Hard, 3 Good, 4 Easy.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answerCards&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&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;cardId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ease&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rating&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;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register them with the SDK:&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;google.antigravity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LocalAgentConfig&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalAgentConfig&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="n"&gt;list_decks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_due_cards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate_card&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;That is one of the nicest parts of the SDK: custom tools do not require a separate server. For this version, I did not need MCP, a framework, a schema generator, or a second process.&lt;/p&gt;

&lt;p&gt;The agent can call plain Python.&lt;/p&gt;

&lt;p&gt;In the real project I ended up with more tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;list_decks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_due_cards&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;show_answer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rate_card&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find_notes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_note&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_notes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update_note&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;suspend_card&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unsuspend_card&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;undo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_stats&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was enough to make the tutor useful.&lt;/p&gt;

&lt;p&gt;This is the first pattern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Put capabilities in tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools are the agent's hands. But hands are not behavior.&lt;/p&gt;

&lt;p&gt;For behavior, I used skills.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Giant System Prompts
&lt;/h2&gt;

&lt;p&gt;At first, I tried to describe everything in the agent's system instructions.&lt;/p&gt;

&lt;p&gt;The tutor needs to know how to run a review session:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;show the question&lt;/li&gt;
&lt;li&gt;wait for my answer&lt;/li&gt;
&lt;li&gt;reveal the answer&lt;/li&gt;
&lt;li&gt;compare my answer&lt;/li&gt;
&lt;li&gt;suggest a rating&lt;/li&gt;
&lt;li&gt;wait for confirmation&lt;/li&gt;
&lt;li&gt;only then update Anki scheduling&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It also needs to know how to write good cards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one fact per card&lt;/li&gt;
&lt;li&gt;answer-first backs&lt;/li&gt;
&lt;li&gt;no trivia padding&lt;/li&gt;
&lt;li&gt;no vague questions&lt;/li&gt;
&lt;li&gt;no giant essay cards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the deck builder needs another workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;research a topic&lt;/li&gt;
&lt;li&gt;extract the important facts&lt;/li&gt;
&lt;li&gt;create cards&lt;/li&gt;
&lt;li&gt;verify they exist in Anki&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the codebase deck builder needs a different workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inspect the repo breadth-first&lt;/li&gt;
&lt;li&gt;find key abstractions&lt;/li&gt;
&lt;li&gt;explain responsibilities and data flow&lt;/li&gt;
&lt;li&gt;avoid making cards for random syntax&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then Telegram needs shorter replies because nobody wants a wall of Markdown on a phone.&lt;/p&gt;

&lt;p&gt;You can put all of that into one system prompt.&lt;/p&gt;

&lt;p&gt;But you should not.&lt;/p&gt;

&lt;p&gt;A giant system prompt has three problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It pollutes every task.&lt;/strong&gt; The agent is thinking about codebase exploration while you are reviewing Spanish verbs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is hard to reuse.&lt;/strong&gt; The same card-writing rules need to appear in the terminal tutor, Telegram tutor, and deck builder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It rots.&lt;/strong&gt; Every new behavior gets pasted into the same blob until nobody knows which rule controls what.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is exactly the problem skills solve.&lt;/p&gt;

&lt;p&gt;The shape changed from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system prompt = tutor rules
              + card-writing rules
              + codebase-exploration rules
              + Telegram style rules
              + safety reminders
              + whatever I forgot last week
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system prompt  = identity + hard safety floor
review-buddy   = study-session behavior
plain-cards    = card-writing behavior
codebase-cards = repo-exploration behavior
hooks/policies = enforcement and receipts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the real pattern behind the title.&lt;/p&gt;

&lt;p&gt;Not "make the prompt better."&lt;/p&gt;

&lt;p&gt;Make the prompt smaller.&lt;/p&gt;




&lt;h2&gt;
  
  
  Skills over System Prompts
&lt;/h2&gt;

&lt;p&gt;A skill is a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; file inside it.&lt;/p&gt;

&lt;p&gt;My project has three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.agents/skills/
  plain-cards/
    SKILL.md
  review-buddy/
    SKILL.md
  codebase-cards/
    SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each skill starts with a tiny bit of frontmatter.&lt;/p&gt;

&lt;p&gt;For example, the review skill begins like this:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review-buddy&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Playbook for running an interactive Anki review session — quiz one card at a time, grade recall together, submit ratings, repair noisy or broken cards.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;description&lt;/code&gt; is not just documentation for humans. It is the lightweight discovery layer. The agent can see what skills exist, then load the full instructions only when the task calls for them.&lt;/p&gt;

&lt;p&gt;A skill is not a service. It is not an MCP server. It is not a deployment. It is a behavior package sitting on disk, ready to be pulled into the agent when needed.&lt;/p&gt;

&lt;p&gt;Then the SDK loads the skill directory:&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="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalAgentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;system_instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_INSTRUCTIONS&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="n"&gt;ALL_TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;skills_paths&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;.agents/skills&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;The key idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The system prompt says who the agent is. Skills say what job it is currently doing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this project, the system prompt stays small. It says the agent is a friendly flashcard tutor working with a real Anki collection. &lt;/p&gt;

&lt;p&gt;The details live in skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;review-buddy&lt;/code&gt;: the study session playbook
&lt;/h3&gt;

&lt;p&gt;This skill describes how to run a review session.&lt;/p&gt;

&lt;p&gt;It covers the rhythm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask one card at a time&lt;/li&gt;
&lt;li&gt;hide the answer until the user attempts it&lt;/li&gt;
&lt;li&gt;reveal and teach briefly&lt;/li&gt;
&lt;li&gt;suggest a rating&lt;/li&gt;
&lt;li&gt;wait for confirmation&lt;/li&gt;
&lt;li&gt;handle noisy or broken cards&lt;/li&gt;
&lt;li&gt;close with a recap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not code. It is behavioral protocol.&lt;/p&gt;

&lt;p&gt;That distinction matters. The review flow is not tied to terminal I/O, Telegram messages, or AnkiConnect. It is just the way a good tutor should behave.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;plain-cards&lt;/code&gt;: the card-writing style guide
&lt;/h3&gt;

&lt;p&gt;This skill handles card quality.&lt;/p&gt;

&lt;p&gt;It tells the agent to write cards that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;atomic&lt;/li&gt;
&lt;li&gt;answer-first&lt;/li&gt;
&lt;li&gt;lean&lt;/li&gt;
&lt;li&gt;verified&lt;/li&gt;
&lt;li&gt;free of filler&lt;/li&gt;
&lt;li&gt;easy to review months later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bad flashcard is worse than no flashcard. It creates fake progress. The model can generate ten cards in seconds, but without a style guide it will happily generate ten vague cards that future me will hate.&lt;/p&gt;

&lt;p&gt;So card writing became a skill.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;codebase-cards&lt;/code&gt;: the repo exploration protocol
&lt;/h3&gt;

&lt;p&gt;This one is for turning source code into Anki cards.&lt;/p&gt;

&lt;p&gt;The agent is told to inspect the repo breadth-first, identify architecture, data flow, responsibilities, and gotchas, then turn only the useful findings into cards.&lt;/p&gt;

&lt;p&gt;That skill powers code mode in the deck builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python deck_builder.py &lt;span class="s2"&gt;"overall architecture"&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; ~/my/project &lt;span class="nt"&gt;--count&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The focus hint changes, but the exploration protocol stays the same.&lt;/p&gt;

&lt;p&gt;This is the second pattern:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Put reusable behavior in skills.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not in the system prompt. Not duplicated across entrypoints. Not buried in Python conditionals.&lt;/p&gt;

&lt;p&gt;A skill is just a file, but it changes the shape of the whole project.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Behavior Layer, Three Surfaces
&lt;/h2&gt;

&lt;p&gt;Once the behavior lived in skills, adding new surfaces became much easier.&lt;/p&gt;

&lt;p&gt;The architecture looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         .agents/skills/
                  ┌──────────┼──────────┐
                  │          │          │
           review-buddy  plain-cards  codebase-cards
                  │          │          │
                  └──────────┼──────────┘
                             │
                    LocalAgentConfig
                             │
       ┌─────────────────────┼─────────────────────┐
       │                     │                     │
  terminal tutor        Telegram tutor        deck builder
    tutor.py          telegram_tutor.py      deck_builder.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal tutor is the simplest surface:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_interactive_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Telegram tutor uses the same agent differently:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Agent&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="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="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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deck builder streams output as it works:&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="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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&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;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&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;Different surfaces. Same runtime. Same skills.&lt;/p&gt;

&lt;p&gt;That is the part I liked most. Telegram did not need a copied review prompt. The deck builder did not need its own card-writing manifesto. The codebase mode did not need a separate app-specific doctrine.&lt;/p&gt;

&lt;p&gt;They all loaded the same skill directory.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Terminal Tutor
&lt;/h2&gt;

&lt;p&gt;The terminal version is the baseline.&lt;/p&gt;

&lt;p&gt;Start Anki, run the tutor, and ask naturally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python tutor.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quiz me on XYZ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tutor lists due cards, asks one question, waits for my answer, reveals the real Anki answer, compares, teaches, and suggests a rating.&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%2Fn5pbky88yc6xycgyeade.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%2Fn5pbky88yc6xycgyeade.png" alt="Screenshot: Terminal tutor answering a flashcard" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important part: it does not update scheduling just because the model thinks I got the answer right.&lt;/p&gt;

&lt;p&gt;The review loop is human-in-the-loop by design:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent: I would rate this Good (3). You had the main idea but missed the date.
User: yes
Agent: rated 3. Next card...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or I can override it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent: I would rate this Hard (2).
User: actually 1
Agent: rated Again (1). Let's reinforce it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spaced repetition is stateful. A bad rating affects the future schedule. So the model can suggest, but I decide.&lt;/p&gt;

&lt;p&gt;That is not just a prompt preference. It is the product boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Telegram Tutor
&lt;/h2&gt;

&lt;p&gt;The second surface was Telegram.&lt;/p&gt;

&lt;p&gt;Not because Telegram is fancy. Because the best study app is the one I actually open.&lt;/p&gt;

&lt;p&gt;The Telegram bot long-polls the Bot API, sends messages into the same Antigravity agent, and returns the response. It also supports voice notes: speak the answer, transcribe it, and feed the transcript back into the tutor as text.&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%2Fwt7nob5n1zhnvtydei5d.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%2Fwt7nob5n1zhnvtydei5d.png" alt="Screenshot: Telegram tutor answering a flashcard from phone" width="505" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent gets a small extra instruction:&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="n"&gt;TELEGRAM_INSTRUCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are chatting through Telegram on a phone. Keep replies short and plain
text only — no markdown headers, tables, or code fences. One card per message.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else stays shared.&lt;/p&gt;

&lt;p&gt;Same Anki tools. Same hooks. Same skills.&lt;/p&gt;

&lt;p&gt;I also added due-card nudges without spending model tokens. Every 30 minutes, plain Python checks Anki deck counts. If cards are waiting, the bot sends a short reminder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;25 cards waiting (X 5, Y 8). Say 'quiz me' to start.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No LLM needed. No reasoning needed. Just deterministic code.&lt;/p&gt;

&lt;p&gt;This became a useful design rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not use the model for work a &lt;code&gt;for&lt;/code&gt; loop can do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent is for tutoring. The nudge is just a counter.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deck Builder
&lt;/h2&gt;

&lt;p&gt;The third surface is a deck builder.&lt;/p&gt;

&lt;p&gt;It has two modes.&lt;/p&gt;

&lt;p&gt;Web mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python deck_builder.py &lt;span class="s2"&gt;"Ottoman Empire"&lt;/span&gt; &lt;span class="nt"&gt;--deck&lt;/span&gt; &lt;span class="s2"&gt;"History"&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codebase mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python deck_builder.py &lt;span class="s2"&gt;"error handling and edge cases"&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; ~/my/project &lt;span class="nt"&gt;--count&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Web mode gives the agent a small research toolset: Wikipedia search, Wikipedia read, and URL fetch. Then it asks the agent to create cards using the &lt;code&gt;plain-cards&lt;/code&gt; skill.&lt;/p&gt;

&lt;p&gt;Codebase mode is more interesting. The SDK can give the agent built-in file tools scoped to a workspace. I enabled read-only access:&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;google.antigravity.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BuiltinTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CapabilitiesConfig&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalAgentConfig&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="n"&gt;add_notes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_decks&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;workspaces&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;code_path&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;CapabilitiesConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;enabled_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BuiltinTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_only&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;skills_paths&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;.agents/skills&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;That means the agent can inspect the target repo, but not edit it.&lt;/p&gt;

&lt;p&gt;For a deck builder, that is the right permission boundary. It needs to read code and create Anki notes. It does not need to modify the project.&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;codebase-cards&lt;/code&gt; activates. The agent explores the repo, identifies the concepts worth remembering, then writes cards through &lt;code&gt;add_notes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the end, I do not trust the model's narration. The script queries Anki to verify the cards exist.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cards_in_anki&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deck&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;int&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;find_notes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;deck:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; tag:auto-researched&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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;len&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&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="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model says it created cards but Anki has zero, the script nudges it to try again.&lt;/p&gt;

&lt;p&gt;That became another rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trust the system receipt, not the model narration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Turning It Ambient with Triggers
&lt;/h2&gt;

&lt;p&gt;The SDK also supports triggers: background tasks that react to external events and push messages into the agent.&lt;/p&gt;

&lt;p&gt;I used a file-change trigger for codebase card generation.&lt;/p&gt;

&lt;p&gt;The idea: while I work on a project, if a Python file changes, the agent can inspect the change and decide whether it introduced something worth remembering.&lt;/p&gt;

&lt;p&gt;Simplified:&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;google.antigravity.triggers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;on_file_change&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_watch_trigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_change&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="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;These files changed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create cards in deck &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; if the change is worth remembering.&lt;/span&gt;&lt;span class="sh"&gt;"&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;on_file_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_change&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python deck_builder.py &lt;span class="s2"&gt;"as I work"&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; ~/my/project &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the project started feeling less like a chatbot and more like a sidecar.&lt;/p&gt;

&lt;p&gt;I edit code. The trigger wakes the agent. The codebase skill tells it how to inspect the change. The card-writing skill tells it how to write good cards. The Anki tool creates the notes.&lt;/p&gt;

&lt;p&gt;No new server. No custom scheduler. No giant prompt.&lt;/p&gt;

&lt;p&gt;Just SDK triggers plus skills.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part I Refused to Trust to the Model
&lt;/h2&gt;

&lt;p&gt;Skills are guidance.&lt;/p&gt;

&lt;p&gt;Policies and hooks are enforcement.&lt;/p&gt;

&lt;p&gt;That line is the difference between a fun demo and a tool I can leave connected to my real Anki collection.&lt;/p&gt;

&lt;p&gt;The Antigravity SDK has declarative safety policies and lifecycle hooks. I used both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice mode blocks scheduling writes
&lt;/h3&gt;

&lt;p&gt;Sometimes I want to cram without touching Anki scheduling.&lt;/p&gt;

&lt;p&gt;A prompt instruction is not enough for that. If the agent forgets and calls &lt;code&gt;rate_card&lt;/code&gt;, the schedule changes.&lt;/p&gt;

&lt;p&gt;So practice mode denies the tool at the harness 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;google.antigravity.hooks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;

&lt;span class="n"&gt;policies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm_run_command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;practice_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;policies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policies&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate_card&lt;/span&gt;&lt;span class="sh"&gt;"&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;practice_mode&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;Now &lt;code&gt;rate_card&lt;/code&gt; is blocked even if the model tries to call it.&lt;/p&gt;

&lt;p&gt;That is the kind of safety I want: not vibes, not trust, not "please don't". A runtime boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hooks sync, back up, audit, and recover
&lt;/h3&gt;

&lt;p&gt;The SDK hook system lets you observe or intervene at lifecycle points.&lt;/p&gt;

&lt;p&gt;I used session hooks to sync Anki:&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="nd"&gt;@hooks.on_session_start&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sync_on_start&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;sync_anki&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@hooks.on_session_end&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sync_on_end&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;sync_anki&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used a pre-tool-call Decide hook to back up a deck before note writes:&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="nd"&gt;@hooks.pre_tool_call_decide&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backup_before_note_writes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add_note&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;add_notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;backup_deck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deck&lt;/span&gt;&lt;span class="sh"&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;hooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HookResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&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;I used a post-tool-call Inspect hook to audit scheduling changes:&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="nd"&gt;@hooks.post_tool_call&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit_scheduling_changes&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="k"&gt;if&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;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rate_card&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;undo&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;suspend_card&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;unsuspend_card&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
        &lt;span class="nf"&gt;append_jsonl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backups/scheduling_audit.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I used a Transform hook to turn ugly tool errors into recovery hints the model can act on:&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="nd"&gt;@hooks.on_tool_error&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recover_from_tool_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AnkiConnect timed out. Ask the user to check Anki, then retry.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the strongest parts of the SDK.&lt;/p&gt;

&lt;p&gt;The model does not need to remember to audit itself. The harness does it.&lt;/p&gt;

&lt;p&gt;The model does not need to remember to back up a deck before writing. The hook does it.&lt;/p&gt;

&lt;p&gt;The model does not get to bypass practice mode. The policy blocks it.&lt;/p&gt;

&lt;p&gt;The pattern became clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;tools&lt;/strong&gt; give the agent capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;skills&lt;/strong&gt; give the agent reusable behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;policies&lt;/strong&gt; define what must never happen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;hooks&lt;/strong&gt; add system-level guarantees around the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation is the architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Worked
&lt;/h2&gt;

&lt;p&gt;A few things worked better than expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plain Python tools were enough
&lt;/h3&gt;

&lt;p&gt;I originally thought I might need to build an MCP server immediately.&lt;/p&gt;

&lt;p&gt;I did not.&lt;/p&gt;

&lt;p&gt;For one application, custom Python functions were simpler. The SDK already knows how to expose them as tools. That kept the first version small.&lt;/p&gt;

&lt;p&gt;MCP is still useful when you want the same tools available across multiple clients. But for an SDK-native app, Python functions are the shortest path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills kept the project from becoming prompt soup
&lt;/h3&gt;

&lt;p&gt;This was the biggest win.&lt;/p&gt;

&lt;p&gt;The base system instructions stayed focused. The detailed workflows moved into skills.&lt;/p&gt;

&lt;p&gt;When I improved card-writing rules, terminal, Telegram, and deck builder all benefited. I did not need to update three prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hooks made side effects less scary
&lt;/h3&gt;

&lt;p&gt;Anki is not a toy database. It is my real spaced-repetition schedule.&lt;/p&gt;

&lt;p&gt;The hooks gave me a deterministic layer around model behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sync at session boundaries&lt;/li&gt;
&lt;li&gt;backup before writes&lt;/li&gt;
&lt;li&gt;audit after scheduling changes&lt;/li&gt;
&lt;li&gt;recover from tool failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made the agent feel much less like a random chatbot with database access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triggers changed the feel of the app
&lt;/h3&gt;

&lt;p&gt;The file watcher was small, but it changed the mental model.&lt;/p&gt;

&lt;p&gt;The agent was no longer only something I talked to. It could react to work happening around it.&lt;/p&gt;

&lt;p&gt;That is where SDK agents get interesting: not just chat, but event-driven labor.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Did Not Work Perfectly
&lt;/h2&gt;

&lt;p&gt;A few caveats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills are not hard guarantees
&lt;/h3&gt;

&lt;p&gt;Skills are instructions. They improve behavior, but they are still model-read guidance.&lt;/p&gt;

&lt;p&gt;If something must be impossible, use a policy or remove the tool.&lt;/p&gt;

&lt;p&gt;That is why practice mode denies &lt;code&gt;rate_card&lt;/code&gt; instead of merely asking the model not to call it.&lt;/p&gt;

&lt;h3&gt;
  
  
  AnkiConnect has sharp edges
&lt;/h3&gt;

&lt;p&gt;AnkiConnect is simple, but it has quirks.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;answerCards&lt;/code&gt; can return success even for bad card IDs unless you pre-check the card. Some note updates silently fail if the note is open in Anki's browser window. AnkiConnect also runs inside Anki's Qt process, so you should not treat it like a high-concurrency API.&lt;/p&gt;

&lt;p&gt;The fix is boring and important: validate inside tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Voice was simpler outside the agent loop
&lt;/h3&gt;

&lt;p&gt;The Telegram bot supports voice answers, but I kept transcription outside the agent loop. A direct Gemini transcription call turns the voice note into text, then the transcript goes into the tutor.&lt;/p&gt;

&lt;p&gt;That was simpler and more reliable for this build.&lt;/p&gt;

&lt;p&gt;The lesson: use the SDK where it makes the architecture cleaner. Do not force every feature through the agent if a direct call is simpler.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Build Something Similar
&lt;/h2&gt;

&lt;p&gt;If you want to build your own version of this pattern, I would do it in this order.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with one real workflow
&lt;/h3&gt;

&lt;p&gt;Do not start with a platform.&lt;/p&gt;

&lt;p&gt;Pick one annoying workflow with real state behind it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flashcards&lt;/li&gt;
&lt;li&gt;GitHub issues&lt;/li&gt;
&lt;li&gt;CRM updates&lt;/li&gt;
&lt;li&gt;personal knowledge base&lt;/li&gt;
&lt;li&gt;support tickets&lt;/li&gt;
&lt;li&gt;finance records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The state matters. Agents get interesting when they can act on something real.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Wrap the system as small Python tools
&lt;/h3&gt;

&lt;p&gt;Keep the tools boring.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s records.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&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="n"&gt;body&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Create a new record after user approval.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register them:&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="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalAgentConfig&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="n"&gt;search_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create_item&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;Make tools validate inputs. Do not rely on the model to pass perfect IDs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Move task behavior into skills
&lt;/h3&gt;

&lt;p&gt;Create a skill folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.agents/skills/my-workflow/SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal skill:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-workflow&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use when helping the user process and update records in this system.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# My Workflow&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Inspect the current record before changing it.
&lt;span class="p"&gt;2.&lt;/span&gt; Propose the change in plain language.
&lt;span class="p"&gt;3.&lt;/span&gt; Wait for user confirmation before writing.
&lt;span class="p"&gt;4.&lt;/span&gt; After writing, verify the record exists.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then load it:&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="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalAgentConfig&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="n"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;skills_paths&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;.agents/skills&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;This is the move: do not keep growing the system prompt forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add policies for non-negotiables
&lt;/h3&gt;

&lt;p&gt;If a tool should never run in a mode, deny it.&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="n"&gt;policies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete_record&lt;/span&gt;&lt;span class="sh"&gt;"&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;no_deletes&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;If shell execution should require confirmation, keep the default guard:&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="n"&gt;policies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirm_run_command&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 can misunderstand a skill. It cannot ignore a denied tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Add hooks for receipts
&lt;/h3&gt;

&lt;p&gt;Use hooks for things that should happen regardless of whether the model remembers them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;audit logs&lt;/li&gt;
&lt;li&gt;backups&lt;/li&gt;
&lt;li&gt;sync&lt;/li&gt;
&lt;li&gt;metrics&lt;/li&gt;
&lt;li&gt;sanitization&lt;/li&gt;
&lt;li&gt;error recovery
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@hooks.post_tool_call&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit&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="nf"&gt;write_log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&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;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&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;error&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;
  
  
  6. Add another surface only after the behavior is reusable
&lt;/h3&gt;

&lt;p&gt;Once the behavior lives in tools and skills, a second surface becomes much cheaper.&lt;/p&gt;

&lt;p&gt;Terminal first. Then Telegram, Slack, web, cron, or file triggers.&lt;/p&gt;

&lt;p&gt;The surface should be thin. The agent behavior should not live there.&lt;/p&gt;




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

&lt;p&gt;The old way to build an AI feature was to write a large prompt and hope the model followed it.&lt;/p&gt;

&lt;p&gt;That is not enough for real agents.&lt;/p&gt;

&lt;p&gt;A real agent needs separation of concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capabilities       → tools
Reusable behavior  → skills
Hard boundaries    → policies
System guarantees  → hooks
External events    → triggers
User interface     → thin surface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the Antigravity SDK made pleasant. I could build one agent runtime and reuse it across terminal, Telegram, and deck generation. I could keep the tutoring behavior in &lt;code&gt;SKILL.md&lt;/code&gt; files instead of duplicating it. I could wrap real side effects with policies and hooks instead of trusting the model to behave.&lt;/p&gt;

&lt;p&gt;The Anki tutor is just the concrete example.&lt;/p&gt;

&lt;p&gt;The pattern generalizes.&lt;/p&gt;

&lt;p&gt;A support agent could keep triage behavior in a skill, expose ticket updates as tools, deny destructive writes by policy, and audit every status change by hook.&lt;/p&gt;

&lt;p&gt;A code review agent could keep review rubrics in skills, expose GitHub as tools, require approval before comments, and verify every posted review.&lt;/p&gt;

&lt;p&gt;A research agent could keep extraction protocols in skills, use file triggers to process new papers, and write structured outputs only after validation.&lt;/p&gt;

&lt;p&gt;The skill is the portable behavior module.&lt;/p&gt;

&lt;p&gt;The SDK is the harness that lets it act.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google-antigravity/antigravity-sdk-python" rel="noopener noreferrer"&gt;Google Antigravity SDK GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://antigravity.google/" rel="noopener noreferrer"&gt;Google Antigravity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://antigravity.google/docs/sdk-overview" rel="noopener noreferrer"&gt;Antigravity SDK overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio API keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.ankiweb.net/" rel="noopener noreferrer"&gt;Anki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I started this because I was too lazy to open Anki.&lt;/p&gt;

&lt;p&gt;That sounds like a joke, but most useful automation starts there. Not with a grand platform vision. With a small workflow that keeps not happening because the friction is just high enough.&lt;/p&gt;

&lt;p&gt;The surprising part was not that an LLM could quiz me.&lt;/p&gt;

&lt;p&gt;The surprising part was how clean the architecture became.&lt;/p&gt;

&lt;p&gt;Tools gave the agent hands. Skills gave it habits. Policies gave it boundaries. Hooks gave it receipts. Triggers made it wake up when something changed.&lt;/p&gt;

&lt;p&gt;That is the version of agents I trust more: not one giant prompt pretending to be an application, but a small runtime with clear layers.&lt;/p&gt;

&lt;p&gt;The future of agent apps is not monolithic complex systems.&lt;/p&gt;

&lt;p&gt;It is smaller prompts, sharper tools, reusable skills, and a harness that refuses to let the model pretend a side effect happened when it did not.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>antigravity</category>
      <category>agents</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Serverless Gemma 12B on Azure Container Apps</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Fri, 19 Jun 2026 02:00:12 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/serverless-gemma-12b-on-azure-container-apps-4i75</link>
      <guid>https://dev.clauneck.workers.dev/gde/serverless-gemma-12b-on-azure-container-apps-4i75</guid>
      <description>&lt;p&gt;This article provides a step by step debugging guide for deploying Gemma 4 to Azure Container Apps. A suite of Python MCP tools is built to simplify management of the vLLM hosted Gemma 4 deployment with Antigravity CLI.&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.amazonaws.com%2Fuploads%2Farticles%2F3360w3vqaf31m7sj2e2u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3360w3vqaf31m7sj2e2u.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is this project trying to Do?
&lt;/h4&gt;

&lt;p&gt;This project is a DevOps/SRE assistant that uses a Gemma 4 model hosted on Azure Container APps. It provides tools to provision the Docker container and deploy the model, as well as for observability and performance testing.&lt;/p&gt;

&lt;p&gt;This project is similar to a previous project that targeted GPU hosted Gemma4 instances on GCP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/gemma-sre-self-hosted-vllm-infrastructure-agent-afda000c0943" rel="noopener noreferrer"&gt;Gemma-SRE: Self-Hosted vLLM Infrastructure Agent&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Installation Instructions
&lt;/h4&gt;

&lt;p&gt;The detailed installation instructions for Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/google-cloud/getting-started-with-antigravity-cli-26c5da90951f" rel="noopener noreferrer"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure CLI
&lt;/h4&gt;

&lt;p&gt;The Azure Command-Line Interface (CLI) is a cross-platform tool used to connect to Azure and execute administrative commands on Azure resources. It allows you to manage services like virtual machines, databases, and networking through a terminal using interactive prompts or scripts. [&lt;a href="https://learn.microsoft.com/en-us/cli/azure/what-is-azure-cli?view=azure-cli-latest" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/cli/azure/?view=azure-cli-latest" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://sumble.com/tech/azure-cli" rel="noopener noreferrer"&gt;3&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;More details are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/overview?tabs=linux" rel="noopener noreferrer"&gt;What is the Azure Developer CLI?&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Container Apps
&lt;/h4&gt;

&lt;p&gt;Azure Container Apps is a fully managed, serverless Kubernetes-based application platform designed for building and deploying modern, containerized apps without managing complex infrastructure. It enables scaling from zero to high demand, supports microservices, and handles event-driven processing with built-in HTTPS and observability.&lt;/p&gt;

&lt;p&gt;Full details are available here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://azure.microsoft.com/en-us/products/container-apps" rel="noopener noreferrer"&gt;https://azure.microsoft.com/en-us/products/container-apps&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.amazonaws.com%2Fuploads%2Farticles%2Fxzsiipedq5e6auvt0f5n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzsiipedq5e6auvt0f5n.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Where do I start?
&lt;/h4&gt;

&lt;p&gt;The strategy for starting MCP development for model management is a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;First, the basic development environment is setup with the required system variables, and a working Antigravity CLI configuration.&lt;/p&gt;

&lt;p&gt;Then, a minimal Python MCP Server is built with stdio transport. This server is validated with Antigravity CLI in the local environment.&lt;/p&gt;

&lt;p&gt;This setup validates the connection from Antigravity CLI to the local server via MCP. The MCP client (Antigravity CLI) and the Python MCP server both run in the same local environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup the Basic Environment
&lt;/h4&gt;

&lt;p&gt;At this point you should have a working Python environment and a working Antigravity CLI installation. The next step is to clone the GitHub samples repository with support scripts:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/xbill9/gemma4-tips-azure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;init.sh&lt;/strong&gt; from the cloned directory.&lt;/p&gt;

&lt;p&gt;The script will attempt to determine your shell environment and set the correct variables:&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="nb"&gt;cd &lt;/span&gt;gpu-12B-qat-aca-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session times out or you need to re-authenticate- you can run the &lt;strong&gt;set_env.sh&lt;/strong&gt; script to reset your environment variables:&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="nb"&gt;cd &lt;/span&gt;gpu-12B-qat-aca-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Management Tool with MCP Stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the standard MCP libraries provide is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;The connection over stdio will look similar to this:&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;# Initialize FastMCP server
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Self-Hosted vLLM DevOps Agent&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;h4&gt;
  
  
  Running the Python Code
&lt;/h4&gt;

&lt;p&gt;First- switch the directory with the Python version of the MCP sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/gemma4-tips-azure/cd gpu-12B-qat-aca-devops-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the release version on the local system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;xbill@penguin:~/gemma4-tips-azure/gpu-12B-qat-aca-devops-agent$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;pip install -r requirements.txt
Requirement already satisfied: mcp in /home/xbill/.pyenv/versions/3.13.13/lib/python3.13/site-packages (from -r requirements.txt (line 1)) (1.27.2)
Requirement already satisfied: fastmcp in /home/xbill/.pyenv/versions/3.13.13/lib/python3.13/site-packages (from -r requirements.txt (line 2)) (2.11.3)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project can also be linted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;mxbill@penguin:~/gemma4-tips-azure/gpu-12B-qat-aca-devops-agent$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make lint
&lt;span class="go"&gt;ruff check .
All checks passed!
ruff format --check .
7 files already formatted
mypy .
Success: no issues found in 7 source files
&lt;/span&gt;&lt;span class="gp"&gt;xbill@penguin:~/gemma4-tips-azure/gpu-12B-qat-aca-devops-agent$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MCP stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the MCP protocol provides is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;In this project Antigravity CLI is used as the MCP client to interact with the Python MCP server code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI mcp_config.json
&lt;/h4&gt;

&lt;p&gt;A sample MCP server file is provided in the .agents directory:&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gpu-devops-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/home/xbill/gemma4-tips-azure/gpu-12B-qat-aca-devops-agent/server.py"&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;"env"&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;"GOOGLE_CLOUD_PROJECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aisprint-491218"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MODEL_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/models/gemma-4-12B-it-qat-w4a16-ct"&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;h4&gt;
  
  
  Validation with Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;The final connection test uses Antigravity CLI as a MCP client with the Python code providing the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
&amp;gt; ✓ google-dev-knowledge Tools: search_documents, answer_query, get_documents
   ✓ gpu-devops-agent Tools: save_hf_token, get_vllm_endpoint, list_vertex_models, list_bucket_models,
                       analyze_cloud_logging, +25 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting Started with Gemma 4 on GPU
&lt;/h4&gt;

&lt;p&gt;The Official vLLM repo also has Gemma4 specific information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm/releases" rel="noopener noreferrer"&gt;Releases · vllm-project/vllm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Gemma 12B model was just released:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/introducing-gemma-4-12b/" rel="noopener noreferrer"&gt;Introducing Gemma 4 12B: a unified, encoder-free multimodal model&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What about the QAT Models?
&lt;/h4&gt;

&lt;p&gt;A deep dive into deploying the 12B QAT model is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xbill999.medium.com/12b-gemma-4-qat-deployment-with-nvidia-l4-cloud-run-mcp-and-antigravity-cli-944d603b4ab5" rel="noopener noreferrer"&gt;12B Gemma 4 QAT Deployment with NVIDIA L4, Cloud Run, MCP, and Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Lets Deploy this!
&lt;/h4&gt;

&lt;p&gt;The model was deployed to Standard_NV36ads_A10_v5 backed with a NVIDIA GPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; status_azure_vm

● ListDir(/home/xbill/.gemini/antigravity-cli/mcp/gpu-devops-agent) 
● Read(/home/xbill/.gemini/antigravity-cli/mcp/gpu-devops-agent/status_azure_vm.json) 
● gpu-devops-agent/status_azure_vm(Call MCP Tool) (ctrl+o to expand)

  I have retrieved the status of the Azure Container App service:

  ### Azure Container App Status for service gpu-12b-qat-l4-devops-agent :

  • Container App Name: gpu-12b-qat-l4-devops-agent-app 
  • Provisioning State: Succeeded 
  • Running Status: Running 
  • FQDN: https://gpu-12b-qat-l4-devops-agent-app.purplesmoke-a1c60b91.eastus.azurecontainerapps.io 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check the Docker Container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; check_vllm

  Here is the verified status output of the check_vllm diagnostic check for the active Azure Container App:

  ### 🖥️ Azure Container App: gemma4-vllm-gpu 

  • Provisioning State: Succeeded 
  • Running Status: Running 
  • FQDN: https://gemma4-vllm-gpu.gentlefield-612b24aa.westus2.azurecontainerapps.io 
  • vLLM API Endpoint ( https://gemma4-vllm-gpu.gentlefield-612b24aa.westus2.azurecontainerapps.io/health ): Healthy ✅ 

  All deployment verification logs have been archived in aca_deployment_status.md.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross Check The Deployed Model
&lt;/h4&gt;

&lt;p&gt;Once the model starts locally- the MCP tool allows for model verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&amp;gt; verify_model_health

  Here are the details from the deep model health check diagnostics:

  • Health Status: PASSED ✅
  • Target Model: google/gemma-4-12B-it-qat-w4a16-ct 
  • Response: 'Yes, I am working! I am Gemma 4, a large language ...' 
  • Latency: 2.23 seconds 

  Full details have been logged in aca_deployment_status.md.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; &amp;gt; check model details

  I will call the get_model_details tool on the MCP server to retrieve the active vLLM serving parameters and
  configurations.

● gpu-devops-agent/get_model_details(Call get_model_details tool) (ctrl+o to expand)

  Here are the serving model details retrieved from the active vLLM instance:

  ### 🧩 Model Details

  • Public Endpoint: https://gemma4-vllm-gpu.gentlefield-612b24aa.westus2.azurecontainerapps.io
  • Model ID: google/gemma-4-12B-it-qat-w4a16-ct 
  • Object Type: model 
  • Owned By: vllm 
  • API Health Endpoint Status: Healthy ✅

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; get_endpoint

  Here is the active vLLM service URL:

  🟢 vLLM Endpoint: https://gemma4-vllm-gpu.gentlefield-612b24aa.westus2.azurecontainerapps.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; get_status

  Here is the verified GPU vLLM System Status:

  ### 🌀 GPU vLLM System Status

  • vLLM Health: 🟢 Online (https://gemma4-vllm-gpu.gentlefield-612b24aa.westus2.azurecontainerapps.io)
  • Hosting Status: 🟢 ACA Succeeded ( gemma4-vllm-gpu )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; get_metrics

  ### 📈 Request &amp;amp; Concurrency Metrics

  • Successfully Processed Requests: 2 (both completed via natural stop token)
  • Currently Active Running Requests: 0 
  • Currently Waiting Requests (Queued): 0 
  • KV-Cache Memory Usage: 0.0% 

  ### 🚀 Throughput &amp;amp; Token Stats

  • Total Prefill (Prompt) Tokens Processed: 36 tokens
  • Total Generated (Completion) Tokens Processed: 63 tokens

  ### ⏱️ Latency Metrics

  • Average Time to First Token (TTFT): 1.81 seconds (sum: 3.62s over 2 requests)
  • Average End-to-End Latency: 2.96 seconds (sum: 5.92s over 2 requests)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Check the Azure Instance
&lt;/h4&gt;

&lt;p&gt;Once Deployed — the model is visible on the Azure Console:&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%2Fpzmrczges68s8ouzjxqs.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%2Fpzmrczges68s8ouzjxqs.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Review the Model
&lt;/h4&gt;

&lt;p&gt;Check the model details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the model details?

  Here are the active model deployment details:

  • Serving Endpoint: https://gemma4-vllm-gpu.gentlefield-612b24aa.westus2.azurecontainerapps.io
  • Model ID: google/gemma-4-12B-it-qat-w4a16-ct 
  • Quantization Format: compressed-tensors (4-bit weights, 16-bit activations)
  • API Engine / Server: vLLM 
  • VRAM Allocation (Tesla T4): 95% GPU memory utilization, 18,187 KV cache tokens
  • API Health status: Healthy (Online) ✅


&amp;gt; query_gemma4_with_stats what is Gemma?

  I will call the query_gemma4_with_stats tool on the MCP server to query the model and retrieve both the generated
  response and model performance statistics (like TTFT and total generation time).

● gpu-devops-agent/query_gemma4_with_stats(Call query_gemma4_with_stats tool) (ctrl+o to expand)

  Here is the response from the serving Gemma 4 model along with the query performance statistics:

  ### 💬 Model Response

  │ Depending on the context, Gemma usually refers to one of two very popular things: a Google AI model or a gemstone.
  │
  │ Here is a breakdown of both:
  │
  │ ### 1. In Technology: Google’s AI Model
  │
  │ Gemma is a family of open-model Large Language Models (LLMs) developed by Google DeepMind.
  │
  │ • Relationship to Gemini: It is built from the same research, technology, and infrastructure used to create Google's
  │ most capable AI model, Gemini.
  │ • "Open" Nature: Unlike the "closed" Gemini models (which are accessed primarily via API or Google products), Gemma
  is
  │ released as "open weights." This means developers and researchers can download, customize, and run the model on their
  │ own hardware (like a personal laptop or a private cloud).
  │ • Size Varieties: Gemma is designed to be lightweight. It comes in different sizes (such as 2B, 7B, 9B, and 27B
  │ parameters), making it easy for people who don't have massive supercomputers to run high-quality AI locally.
  │ • Capabilities: It can understand and generate text, reason, and even process images or audio (depending on the
  │ version). It is commonly used by developers to build chatbots, summarize documents, or power coding assistants.
  │ ──────
  │ ### 2. In Gemology: The Gemstone
  │
  │ Gemma is the Latin word for "gem" or "precious stone." In the world of jewelry and geology:
  │
  │ • Gemma is often used as a prefix or root for various precious stones (e.g., Gemmae refers to jewels).
  │ • However, if you are looking for a specific stone, you might be thinking of Garnet, Garner, or a specific variety of
  │ Jade.
  │ ──────
  │ ### 3. In Other Contexts
  │
  │ • Name: Gemma is a popular female given name of Italian origin, meaning "gem" or "precious stone."
  │ • Software/Coding: There are various smaller libraries or internal projects in coding communities named "Gemma," but
  │ they are less common than the Google AI model.
  │
  │ Summary: If you are asking about AI, Gemma is Google's "mini" version of Gemini that anyone can download and use for
  │ free.

  ### 📊 Performance Stats

  • Serving Model: google/gemma-4-12B-it-qat-w4a16-ct 
  • Time to First Token (TTFT): 0.26 seconds 
  • Total Generation Time: 19.68 seconds 
  • Generation Throughput: 25.74 tokens/s (~500 tokens total)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benchmark the Local Model
&lt;/h4&gt;

&lt;p&gt;The local model was benchmarked. Summarize the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; summarize benchmark_report.md

  Here is a summary of the performance results from benchmark_report.md:

  ### 🕒 Average Latency Highlights

  • Low Concurrency (1–4 users): Extremely fast response times ( 0.33s – 1.5s ) for all valid context windows (up to
  2048 tokens).
  • High Concurrency (1024–2048 users): Latency scales up to 23s – 32s due to queuing under heavy load.
  • Tipping Point: Latency remains under 3s up to 128 concurrent users for short prompts (&amp;lt;= 64 tokens) and under 8s 
  for large prompts (512 tokens).

  ### 🚀 Throughput Highlights

  • Max Throughput: Tops out at 47.7 requests/sec (4 tokens context window at 1024 users).
  • Prefabricated Prefill Impact: Throughput drops linearly as context window size increases, reaching a maximum
  throughput of 5.2 requests/sec at 2048 tokens.
  • Concurrency Saturation: Throughput plateaus once concurrency reaches 128–256 users; additional users increase latency
  rather than total request throughput.

  ### 🚫 Limit Violations

  • 4096 tokens and above: Reports 0.0% success rate and 0.0 throughput across all concurrencies because the prompt
  length exceeds the serving engine's maximum limit ( max-model-len = 4096 ).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is visualized:&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%2F7gfp6c2d5cz78s5ih4mc.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%2F7gfp6c2d5cz78s5ih4mc.png" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  GPU Memory Allocation of QAT 12B Models
&lt;/h4&gt;

&lt;p&gt;The GPU provides 28 GB of GPU Memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; &amp;gt; what is the memory breakdown on the GPU?
  Here is the approximate memory breakdown on the NVIDIA Tesla T4 GPU (16 GB VRAM) running the google/gemma-4-12B-it-qat-
  w4a16-ct model via vLLM:

  ### 📟 VRAM Allocation Overview (16 GB Total)

   VRAM Category | Size (GiB) | % of total GPU | Purpose
  ---------------------------|--------------|----------------|-----------------------------------------------------------
   Model Weights | ~ 6.50 GiB | ~ 40.6% | Gemma-4 12B QAT 4-bit weights
   KV Cache | 5.85 GiB | 36.6% | Reserved space for context tokens ( 18,187 FP8 tokens)
   CUDA / Workspace Overhead | ~ 2.85 GiB | ~ 17.8% | Run-time activations, CUDA context, graph capture
   Free / System Reserve | 0.80 GiB | 5.0% | Unallocated (enforced by --gpu-memory-utilization 0.95 )
  ──────
  ### 🔍 Key Metrics Details

  1. Model Compression Advantage:
      • An unquantized 12B parameter model in bfloat16 requires at least 24 GB of VRAM just for the weights (which
      exceeds the Tesla T4's 16 GB limit).
      • Thanks to QAT (4-bit quantization), weights occupy only ~6.5 GB, leaving ample space (5.85 GB) for the KV cache
      on a single cost-effective T4 GPU.
  2. KV Cache Efficiency:
      • Quantized to FP8 (via --kv-cache-dtype fp8 ), cutting the token memory footprint in half.
      • This provides a total capacity of 18,187 tokens (5,990 blocks of size 16), which translates to a maximum
      concurrency of 4.44x concurrent users at a full 4,096 context length.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What are the performance characteristics of the Model?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the performance characteristics of the deployment

Here are the core performance characteristics of the vLLM deployment running the Gemma-4 12B QAT model on an NVIDIA    
  Tesla T4 GPU (Azure Container Apps):

### 1. Latency Profile (TTFT &amp;amp; E2E)

• Time to First Token (TTFT): 0.26 seconds under low concurrency, indicating highly optimized prefill compilation.
  • Average E2E Latency: 2.96 seconds for a standard completion task.
  • Queuing Scaling: Latency scales linearly with user concurrency once the queue saturates (reaching ~21s at 256 users
  and ~31s at 1024+ users).

### 2. Throughput &amp;amp; Scaling

• Prefill Throughput Peak: Achieves 47.7 requests per second for very small context queries (4 tokens) at concurrency
  1024.
  • Payload Throughput Bounds: Peak throughput drops to 5.2 requests per second at the model's active 2048-token context
  window boundary.
  • Saturation Plateau: The deployment saturates around 128–256 concurrent users. Adding users beyond this point
  increases queuing latency without improving aggregate throughput.

### 3. Context &amp;amp; Limits

• Maximum Context Size: Strictly capped at 4096 tokens (as configured by --max-model-len ). Queries equal to or larger
  than 4096 tokens are rejected instantaneously (0% success rate).
  • KV-Cache Pool Size: Accommodates 18,187 concurrent tokens using FP8 KV cache quantization.

### 4. Hardware Efficiency (The QAT Advantage)

• Datacenter Accessibility: Quantization-Aware Training (QAT) fits the 12B parameter model into a cost-efficient 16 GB 
  Tesla T4 GPU while reserving 5.85 GB for the active cache.
  • Without QAT, this model would require an A10 or L4 GPU (24 GB VRAM) to start up successfully.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One last cross check — what are the trade-offs of the deployment?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; what are the trade-offs on the deployment?

Here are the primary technical and financial trade-offs in this deployment architecture:

### 1. Model Quantization: QAT 4-bit (INT4) vs. Full Precision (BF16)

• The Trade-off: VRAM Savings vs. Mathematical Precision
  • Pros: Quantization-Aware Training (QAT) reduces model size to ~6.5 GB, allowing a 12B model to be served on a cheap
  16 GB GPU while leaving room for the KV cache.
  • Cons: There is a minor quality degradation in reasoning and function-calling precision compared to the native BF16
  checkpoint (though QAT retains ~99% of baseline performance, far outperforming standard Post-Training Quantization).

### 2. GPU Hardware: Tesla T4 vs. NVIDIA L4

• The Trade-off: Infrastructure Cost vs. Generation Speed
  • Pros: The Tesla T4 (NC8as-T4 workload profile on Azure) is significantly cheaper and more widely available in cloud
  regions than modern NVIDIA L4 GPUs.
  • Cons: The T4 runs on the older Turing architecture and lacks native hardware Tensor Core support for bfloat16 
  execution. This results in slower token generation speeds (~25 tokens/s) than an L4 GPU.

### 3. KV Cache Format: FP8 vs. FP16 Cache

• The Trade-off: Concurrency Capacity vs. Long-Context Recall
  • Pros: Compressing the KV cache to 8-bit ( --kv-cache-dtype fp8 ) cuts memory requirements in half, increasing token
  capacity to 18,187 tokens.
  • Cons: Quantizing attention keys and values can introduce small rounding errors in attention calculations, slightly
  degrading retrieval accuracy in long-context prompts (approaching the 4K boundary).

### 4. Active Context Cap: 4,096 tokens vs. 32,768 tokens (Model Native)

• The Trade-off: Server Stability vs. Maximum Document Size
  • Pros: Capping the context at 4096 tokens ( --max-model-len 4096 ) guarantees the system will not exhaust VRAM or
  crash under massive concurrent requests.
  • Cons: The model cannot process long documents, large codebases, or extended chat histories that exceed 4K tokens,
  rejecting them immediately.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  So where is the best place to run Gemma? AWS or GCP? Azure?
&lt;/h4&gt;

&lt;p&gt;Survey says…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; compare aws vs gcp vs azure performance

&amp;gt; summarize cloud_comparison_report.md

  Here is a quick summary of cloud_comparison_report.md:

  ### 📋 Key Metrics Comparison

   Metric | AWS EC2 (VM + L4) | GCP Cloud Run (Serverless + L4) | Azure ACA (Serverless + T4)
  ---------------------------|---------------------------|---------------------------------|-----------------------------
   Max Context Size | 16,384 tokens | 16,384 tokens | 4,096 tokens
   Peak Throughput | 93.3 req/s | 40.3 req/s | 47.7 req/s
   First Token Latency | 0.09s | 0.14s | 0.26s
   Max Stable Users | 1024 | 512 | 256

  ### 💡 Core Takeaways

  1. AWS EC2 (L4 GPU VM): Best raw performance and highest stability under extreme concurrency (1024+ users) since
  dedicated hardware avoids serverless hypervisor overhead.
  2. GCP Cloud Run (L4 GPU Serverless): Best cost-performance balance. Supports large context lengths (16K) and scales to
  zero when idle, but throughput is lower than dedicated VMs due to proxy networking.
  3. Azure Container Apps (T4 GPU Serverless): Good for serverless workloads but constrained by older GPU hardware (Tesla
  T4), restricting context length to 4K to prevent Out-of-Memory crashes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  What about costs?!?
&lt;/h4&gt;

&lt;p&gt;The serving costs were summarized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; summarize azure_cost_comparison.md

  Here is a summary of azure_cost_comparison.md:

  ### 📋 Cost Model Summary

  • Azure Container Apps (ACA - Serverless): Billed per second of active execution. Replicas scale down to zero when idle,
  incurring no compute charges.
      • Tesla T4 Profile: ~$2.20/hr. Cheapest option for intermittent use (costs ~$316/month at 20% active utilization).
      • A10G Profile: ~$3.90/hr. Best for serverless workloads that require a larger 24 GB VRAM GPU.
  • Dedicated GPU VM (Standard_NV36ads_A10_v5): Billed hourly at ~$3.20/hr regardless of utilization (costs ~$2,         
  304/month). Offers the cheapest cost-per-hour for 24/7 continuous operations but cannot scale to zero.

  ### 💡 Decision Matrix

  • Choose ACA (T4 or A10G) if your SRE diagnostic workloads are trigger-based, intermittent, or idle for a majority of
  the day.
  • Choose Dedicated VM (A10G) if you are running continuous, high-concurrency production inference pipelines 24/7.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about other cloud costs?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; summarize multi_cloud_cost_comparison.md

  Here is a quick summary of multi_cloud_cost_comparison.md:

  ### 📋 Cost and VM Performance Characteristics

  • GCP Compute Engine ( g2-std-8 ): Cheapest VM option at ~$0.85/hr (~$612/month) for a dedicated NVIDIA L4 GPU.
  • AWS EC2 ( g6.2xlarge ): Highly competitive dedicated VM rate at ~$0.98/hr (~$705/month) with unmatched stability
  under high concurrency.
  • GCP Cloud Run (Serverless L4): Best serverless choice. Costs ~$1.48/hr (only ~$213/month at 20% active runtime) and
  hosts a modern 24 GB VRAM L4 GPU.
  • Azure Container Apps (Serverless T4): More expensive (~$2.20/hr) for an older 16 GB VRAM Tesla T4 GPU, restricting
  active context length to 4K.
  • Azure VM ( NV36ads_A10_v5 ): Most expensive VM option (~$3.20/hr) due to larger bundled CPU/RAM profiles.

  ### 💡 Recommendation

  • If migrating to save cost on scale-to-zero (intermittent load): Choose GCP Cloud Run.
  • If migrating for continuous, high-concurrency 24/7 runtimes: Choose a dedicated GCP Compute Engine VM or AWS EC2 VM.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The strategy for using MCP for Gemma 4 GPU deployment with Antigravity CLI and Azure was validated with an incremental step by step approach.&lt;/p&gt;

&lt;p&gt;A minimal stdio transport MCP Server was started from Python source code and validated with Antigravity CLI running as a MCP client in the same local environment. This Python server provided all of the management tools to deploy and troubleshoot Azure Model deployments.&lt;/p&gt;

</description>
      <category>antigravitycli</category>
      <category>aca</category>
      <category>mcps</category>
      <category>azure</category>
    </item>
    <item>
      <title>Firebase Midsommer Madnesss with Antigravity CLI</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Fri, 19 Jun 2026 00:18:39 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/firebase-midsommer-madnesss-with-antigravity-cli-3geb</link>
      <guid>https://dev.clauneck.workers.dev/gde/firebase-midsommer-madnesss-with-antigravity-cli-3geb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.clauneck.workers.dev/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This installment brings a Firebase build to Midsommer Madness via Antigravity CLI. The Midsommer Madness web app is deployed to Firebase Web Hosting and the Maypole was saved!&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%2Fjk3r10txj28wnssbtu92.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%2Fjk3r10txj28wnssbtu92.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Built
&lt;/h3&gt;

&lt;p&gt;When it comes to Summar Solstace — the place to be is Sweden. It is one of the highlights of the calendar.&lt;/p&gt;

&lt;p&gt;This project aimed to recreate some of the mystique around the event- just in time for some fresh surestromming!&lt;/p&gt;

&lt;p&gt;Now you can get it with Firebase enhancements!&lt;/p&gt;

&lt;h4&gt;
  
  
  FireBase Console
&lt;/h4&gt;

&lt;p&gt;The full project was imported to the Firebase Console:&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%2F4xv29arabo8f1z0t6o1p.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%2F4xv29arabo8f1z0t6o1p.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Just show me the Code!!!
&lt;/h4&gt;

&lt;p&gt;GitHub Repo is here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/xbill9/midsommer-firebase" rel="noopener noreferrer"&gt;GitHub - xbill9/midsommer-firebase&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Midsommer Madness with Firebase
&lt;/h3&gt;

&lt;p&gt;So how can we use Firebase to enhance the project?&lt;/p&gt;

&lt;p&gt;Github and Skills!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  The GitHub repository has been successfully linked to your Firebase project midsommer-madness .

  ### What was configured:

  1. GitHub Secrets: A service account ( github-action-1273534909 ) with Firebase Hosting admin permissions was
  created, and its credentials were uploaded to your GitHub repository secrets as                                 
  FIREBASE_SERVICE_ACCOUNT_MIDSOMMER_MADNESS .
  2. GitHub Actions Workflows: Two workflow files were generated and pushed to your GitHub repository:
      • firebase-hosting-pull-request.yml: Deploys a preview channel of the game whenever a PR is created against
the master branch.
      • firebase-hosting-merge.yml: Automatically deploys the latest version to the live Firebase Hosting site
whenever a PR  
      is merged (or a commit is pushed) to the master branch.
  3. Agent Skills: Installed Firebase Agent Skills in the repository to make it easier for agents to help you with
  Firebase configurations in the future.

&amp;gt; /skills
  ⎿ Exited /skills command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What else?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; how is firebase enhancing the project

● Read(/home/xbill/midsommer-firebase/lib/main.dart) (ctrl+o to expand)

  Firebase enhances the Midsommer Madness project in four primary ways, turning it from a simple local game into a
  connected, production-ready mobile application:

  ### 1. 🏆 Global Cloud Leaderboard (Cloud Firestore)

  • Real-Time Synchronization: Instead of keeping scores local to one device, it uses Firestore to sync high
  scores globally. Players are ranked in real time against others on a Top 10 leaderboard.
  • Offline Resiliency: It acts as a fallback system. If a player is offline, the app saves scores to device cache
  ( SharedPreferences on mobile, localStorage on web). Once connectivity returns, it synchronizes gracefully
  without crashing or interrupting gameplay.

  ### 2. 📊 Gameplay Analytics (Firebase Analytics)

  • Event Tracking: Telemetry events like leaderboard_fetched and high score submissions are tracked.
  • Player Behavior: It records details such as the submitted score, the levels played, and character names,
  providing insight into game balance, player engagement, and high score distributions.

  ### 3. 🔍 Error Tracking &amp;amp; Stability (Firebase Crashlytics)

  • Dart &amp;amp; Flutter Crash Reporting: Captures native app crashes or Flutter rendering exceptions instantly.
  • JavaScript Bridge Error Logging: Uncaught JavaScript errors inside the WebView game loop are caught by game. 
  js and forwarded to Crashlytics via the LeaderboardChannel JavaScript channel. This allows you to debug
  client-side web bugs directly inside the Firebase Console.

  ### 4. ⚡ Performance Metrics (Firebase Performance Monitoring)

  • Custom Network Traces: Measures the request latency of fetching and saving leaderboard scores (using custom
  traces get_leaderboard_scores and save_leaderboard_score ).
  • Slow Load Detection: Ensures that network calls to Firestore do not freeze the UI or lag the 60Hz physics
  update loop of the game.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  My Lingonberries are ripening- get to work!
&lt;/h4&gt;

&lt;p&gt;How do you deploy this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;make&lt;/span&gt; &lt;span class="err"&gt;help&lt;/span&gt;
&lt;span class="err"&gt;●&lt;/span&gt; &lt;span class="err"&gt;Bash(make&lt;/span&gt; &lt;span class="err"&gt;help)&lt;/span&gt; &lt;span class="err"&gt;(ctrl+o&lt;/span&gt; &lt;span class="err"&gt;to&lt;/span&gt; &lt;span class="err"&gt;expand)&lt;/span&gt;
  &lt;span class="nl"&gt;Here is the updated output of the make help command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

    &lt;span class="err"&gt;========================================================================&lt;/span&gt;                                      
    &lt;span class="err"&gt;🇸🇪&lt;/span&gt; &lt;span class="err"&gt;Midsommer&lt;/span&gt; &lt;span class="err"&gt;Madness&lt;/span&gt; &lt;span class="err"&gt;Flutter&lt;/span&gt; &lt;span class="err"&gt;Build&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;Development&lt;/span&gt; &lt;span class="err"&gt;Controls&lt;/span&gt; &lt;span class="err"&gt;🇸🇪&lt;/span&gt;                                                
    &lt;span class="err"&gt;========================================================================&lt;/span&gt;                                      
    &lt;span class="nl"&gt;Available commands&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                                                                                           
      make dev - Start the &lt;span class="nb"&gt;local &lt;/span&gt;web server &lt;span class="k"&gt;for &lt;/span&gt;browser play &lt;span class="o"&gt;(&lt;/span&gt;from assets/&lt;span class="o"&gt;)&lt;/span&gt;                              
      make build-apk - Compile the Flutter App and build Debug APK                                             
      make build-ios - Compile the Flutter App and build iOS app &lt;span class="o"&gt;(&lt;/span&gt;no codesign&lt;span class="o"&gt;)&lt;/span&gt;                                 
      make clean - Clean Flutter build outputs and temporary caches                                        
      make logcat - Monitor application logs using Flutter logger                                           
      make deploy - Deploy the game to Firebase Hosting &lt;span class="o"&gt;(&lt;/span&gt;manual bypass&lt;span class="o"&gt;)&lt;/span&gt;                                     
      make firebase-logs - Fetch the latest cloud logs from Google Cloud / Firebase                               
      make firebase-emulators - Start &lt;span class="nb"&gt;local &lt;/span&gt;Firebase Emulator Suite &lt;span class="o"&gt;(&lt;/span&gt;Firestore &amp;amp; Hosting&lt;span class="o"&gt;)&lt;/span&gt;                         
      make deploy-preview - Deploy a temporary preview channel to Firebase Hosting                                
      make firebase-status - Check current Firebase project configurations                                        
      make deploy-rules - Deploy security rules &lt;span class="k"&gt;for &lt;/span&gt;Cloud Firestore                                               
      Note: Pushing/merging to &lt;span class="s1"&gt;'master'&lt;/span&gt; on GitHub automatically triggers deployment.                              
    &lt;span class="o"&gt;========================================================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting Firebase logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; make firebase-logs

● Bash(make firebase-logs) (ctrl+o to expand)
  Here are the latest Firebase/GCP cloud logs fetched from your project:

    TIMESTAMP SEVERITY TYPE TEXT_PAYLOAD MESSAGE
    2026-06-18T16:34:32.784748Z NOTICE audited_resource
    2026-06-18T16:33:37.512215Z NOTICE audited_resource
    2026-06-18T16:11:41.516983584Z NOTICE service_account
    2026-06-18T16:11:40.698356Z NOTICE project
    2026-06-18T16:11:40.054007713Z ERROR service_account
    2026-06-18T16:10:45.753536986Z ERROR service_account
    2026-06-18T16:10:29.417088907Z NOTICE service_account
    2026-06-18T15:43:46.907263Z NOTICE audited_resource
    2026-06-18T15:38:51.598698444Z NOTICE audited_resource
    2026-06-18T15:38:49.452718Z NOTICE audited_resource
    2026-06-18T15:32:56.365232Z NOTICE audited_resource
    2026-06-18T15:32:37.077690Z NOTICE audited_resource
    2026-06-18T15:31:45.425200Z NOTICE project
    2026-06-18T15:31:43.935218Z ERROR project
    2026-06-18T15:31:42.933069Z ERROR project
    2026-06-18T15:28:42.228633Z NOTICE project
    2026-06-18T15:28:41.473570627Z NOTICE service_account
    2026-06-18T15:27:38.230991Z NOTICE project
    2026-06-18T15:27:37.538742814Z NOTICE audited_resource
    2026-06-18T15:27:37.168036Z NOTICE audited_resource

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Getting Firebase Status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make firebase-status
&lt;span class="go"&gt;
● Bash(make firebase-status) (ctrl+o to expand)

  Here is the output of the make firebase-status command:

    Checking active Firebase project list...                                                                      
    npx -y firebase-tools projects:list                                                                           
    - Preparing the list of your Firebase projects                                                                
    ✔ Preparing the list of your Firebase projects                                                                
      make install-apk - Install the compiled debug APK on a connected device/emulator                           
    ✔ Preparing the list of your Firebase projects
    ┌──────────────────────┬─────────────────────────────┬────────────────┬──────────────────────┐
    │ Project Display Name │ Project ID │ Project Number │ Resource Location ID │
    ├──────────────────────┼─────────────────────────────┼────────────────┼──────────────────────┤
    │ Midsommer Madness │ midsommer-madness (current) │ 8002619985 │ [Not specified] │
    └──────────────────────┴─────────────────────────────┴────────────────┴──────────────────────┘

  The active project is correctly marked as midsommer-madness (current).
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  App Debugging
&lt;/h4&gt;

&lt;p&gt;The local Antigravity CLI has full access to the source code and with the Firebase skills — it can be use for remote debugging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  I found the root causes of the freeze/shaking behavior on Game Over:

  ### 1. 🐛 HTML Nesting Bug (Why it didn't end / show Game Over)

  There was a missing closing &amp;lt;/div&amp;gt; tag for the #screenIntro (Level Intro) overlay in index.html.

  • The issue: Because the intro screen div was never closed, the Game Over Screen ( #screenGameOver ) and Victory
  Screen ( #screenVictory ) divs were parsed as nested children of the intro screen.
  • The result: Since the intro screen is hidden ( display: none; ) during gameplay, the Game Over screen was also
  hidden by inheritance—even when the game engine correctly updated its class to .active ! The screen stayed
  blank, showing only the canvas background.

  ### 2. 📳 Screen Shake Jitter Loop (Why it stayed "gittery")

  • The issue: When you die or take damage, the game triggers a screen shake ( shakeIntensity &amp;gt; 0 ). This
  intensity is normally decremented and decayed back to 0 inside the game's update() loop.
  • The result: However, on death, the game state immediately transitions to GAMEOVER , which halts the update()
  loop. Because update() was no longer running, shakeIntensity was never decremented, causing the rendering
  engine to render the canvas with a randomized shake offset on every frame forever.
  ### 🚀 Solution Applied:
  • HTML Fix: Correctly closed the #screenIntro overlay in index.html so that the Game Over card sits on its
  own layer.
  • State Manager Fix: Added shakeIntensity = 0; to both the GAMEOVER and VICTORY state transitions in
  game.js to immediately neutralize any ongoing screen shake when the game ends.

  These fixes have been committed and pushed to master , and the live version at https://midsommer-              
  madness.web.app is currently auto-updating. Try refreshing the game page in a minute to test the fix!

────────────────────────────────────────────────────────────
&amp;gt; how can I test local

  You can test the game locally in a few different ways depending on what you want to test:

  ### 1. 🌐 Test Web Game in Browser (Fastest)

  To test game changes locally in your browser:

    make dev                                                                                                      

  • What it does: Starts a lightweight HTTP server on your machine serving the static files inside the assets/ 
  directory.
  • How to play: Open your browser and go to the local address outputted in your console (e.g.,                   
  http://localhost:8080 ).
  ──────
  ### 2. 🔥 Test Firebase Features Locally (Emulators)

  To test Firestore leaderboard database sync and mock hosting without touching production data:

    make firebase-emulators                                                                                       

  • What it does: Starts the local Firebase Emulator Suite (hosting, firestore database, etc.) on your machine.
  • How to play: Open the local hosting emulator link printed in your terminal. You can check the database records
  in the emulator UI dashboard (usually http://localhost:4000 ).
  ──────
  ### 📱 3. Test the Android App Wrapper

  If you have an Android device or emulator running:
  1. Build the debug APK:
    make build-apk                                                                                                

  2. Install it on your device/emulator:
    make install-apk                                                                                              

  3. Monitor the live app logs for errors/messages:
    make logcat                                                                                                   

────────────────────────────────────────────────────────────
&amp;gt; make dev

● Bash(make dev)
● ManageTask(Task: cccfd6c1-93d3-48aa-be84-ea0d884dfe4f/task-323) (ctrl+o to expand)

  The local development server has been started in the background.

  You can play and test the game locally in your browser at:
  👉 http://127.0.0.1:8081

────────────────────────────────────────────────────────────
&amp;gt; ok local version works. how do I test web

● Bash(gh run list --limit 3) (ctrl+o to expand)

  The automated GitHub Action deployment workflow for your latest fix ("Fix nested screenGameOver...") has already
  completed successfully!

  You can play and test the live web version right now at:
  👉 https://midsommer-madness.web.app

  (Note: If you don't see the changes immediately, you may need to force-refresh your browser—usually Ctrl + F5  
  or Cmd + Shift + R —to clear your browser's local asset cache).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Run it on the Web!
&lt;/h4&gt;

&lt;p&gt;The current version is now available at:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://midsommer-madness.web.app/" rel="noopener noreferrer"&gt;Midsommer Madness - Swedish Retro Arcade&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://midsommer-madness.web.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Watch the Leader Board
&lt;/h4&gt;

&lt;p&gt;Looks like Rolfe has been skilling up!&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%2Fen2dnd6akwr395y9bbxw.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%2Fen2dnd6akwr395y9bbxw.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Level Design
&lt;/h4&gt;

&lt;p&gt;The key levels include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IKEA Warehouse:&lt;/strong&gt; Battle crowded, flatpack-carrying shoppers who throw box projectiles at you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Systembolaget:&lt;/strong&gt; The state-owned liquor shop crowded with drunk Swedes stumbling and lobbing green beer bottles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lördagsgodis:&lt;/strong&gt; Sugar rush Saturday! Dodge hyperactive, strung-out Swedish kids throwing sweet candy projectiles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Swedish Pub:&lt;/strong&gt; Sing along with Frank Zappa fans singing “Bobby Brown” (shouting and firing glowing letters B, O, B, B, Y).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volvo Highway:&lt;/strong&gt; A survival lane-crossing level where…&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  This is Fun! How do I add my own levels?
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI has full visibility into the game:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; add a godis level where strung out kids throw lordagsgodis ▸ Thought for 2s, 525 tokens Considering Level Placement I will search for level ID checks referencing level 9 in game.js to prepare for adding a 10th level. ● Search(Search for lvl.id === 9 checks) (ctrl+o to expand) I will view lines 495 to 585 in game.js to inspect the current level configurations. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) ▸ Thought for 2s, 1.8k tokens Sequencing the Levels I will view the setLevelAudioMode method in game.js to prepare for adding Level 3 "Lördagsgodis". ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will update the setLevelAudioMode method in game.js to map the scales and tempos for the 10-level layout. ● Edit(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view the current level checks in tickSequencer inside game.js to prepare the next set of replacements. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will shift the level conditionals in the music sequencer tickSequencer method to accommodate the 10-level progression. ● Edit(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 920 to 935 of game.js to locate the exact Maypole check in initLevel . ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 1060 to 1070 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 1290 to 1300 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 1360 to 1375 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 1370 to 1385 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 1460 to 1475 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will update the level ID checks for the final Maypole level from 9 to 10 and register the candy-throwing kids target name in the HUD. ● Edit(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view the start of the levels array in game.js to prepare the level insertion edit. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 545 to 585 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) ▸ Thought for 3s, 1.2k tokens Refining The Level Design I will update the levels array in game.js to insert Lördagsgodis as Level 3, shift subsequent level IDs, and re-calculate scores. ● Edit(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 950 to 985 of game.js to locate the spawnEnemy switch statement. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 985 to 1010 of game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will add the "kid" case to the spawnEnemy switch block. ● Edit(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) ▸ Thought for 3s, 2.1k tokens Considering Projectile Design I will search for class ZappaFan in game.js to locate a suitable place to insert CandyKid and Lordagsgodis . ● Search(Search for ZappaFan in game.js) (ctrl+o to expand) I will view lines 2120 to 2145 in game.js. ● Read(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will insert the CandyKid and Lordagsgodis class definitions in game.js right after the Drunkard class. ● Edit(/home/xbill/midsommer-madness/game.js) (ctrl+o to expand) I will view lines 275 to 325 of index.html. ● Read(/home/xbill/midsommer-madness/index.html) (ctrl+o to expand) I will update index.html to add Level 3 Lördagsgodis to the sidebar levels tracker and update levels 4 through 10. ● Edit(/home/xbill/midsommer-madness/index.html) (ctrl+o to expand) I will run a syntax check on game.js using node to ensure the new classes and level configurations are syntax-error free. ● Bash(node -c game.js) (ctrl+o to expand) I will view lines 20 to 45 of game_overview.md to inspect the levels sequence list. ● Read(/home/xbill/.gemini/antigravity-cli/brain/a...2ba-4622-b252-003672e1b73f/game_overview.md) (ctrl+o to expand) I will update game_overview.md to describe the 10-level layout including Lördagsgodis. ● Edit(/home/xbill/.gemini/antigravity-cli/brain/a...2ba-4622-b252-003672e1b73f/game_overview.md) (ctrl+o to expand) I have successfully added the Lördagsgodis level to Midsommer Madness, featuring hyperactive, sugar-rushed children throwing Saturday candy at Sven!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary of Changes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Level Setup ( game.js ): * Inserted Lördagsgodis as game.js in the levels array, shifting following levels up to Level 10 (ABBA Disco). * Updated all final-level check logic throughout game.js from Level 9 to Level 10.&lt;/li&gt;
&lt;li&gt;Added CandyKid &amp;amp; Lordagsgodis Classes ( game.js ):
* Defined game.js and game.js right after the Drunkard class.
* Behavior:
* CandyKid has high speed (1.8), low health (35), and throws sweets rapidly.
* Kids shiver/vibrate rapidly on screen to depict their extreme sugar rush.
* Lordagsgodis are fast-moving candy projectiles (round jelly drops, square sweet gel blocks, and candy
star crosses) that rotate in flight and come in a variety of bright colors.&lt;/li&gt;
&lt;li&gt;Music &amp;amp; Audio:
* Modified game.js to include a happy, hyperactive 150 BPM C Major chiptune track for Level 3
(Lördagsgodis).
* Shifted level conditions in game.js to map Levels 8, 9, and 10 to their respective custom chiptune
hooks.&lt;/li&gt;
&lt;li&gt;UI &amp;amp; Documentation:
* renumerated the sidebar tracker list in index.html to show the 10-level layout.
* Updated the level progression descriptions in game_overview.md.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Phew! That was a lot!
&lt;/h4&gt;

&lt;p&gt;This installment covered extending the Midsommer madness app with Firebase tools to back the web based versions. Future enhancements under consideration include using crashalytics across mobile platforms.&lt;/p&gt;

</description>
      <category>midsommar</category>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Debugging Deployments with Gemma 12B, TPU v6e-1, MCP, and Antigravity CLI</title>
      <dc:creator>xbill</dc:creator>
      <pubDate>Wed, 17 Jun 2026 15:04:24 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/debugging-deployments-with-gemma-12b-tpu-v6e-1-mcp-and-antigravity-cli-83n</link>
      <guid>https://dev.clauneck.workers.dev/gde/debugging-deployments-with-gemma-12b-tpu-v6e-1-mcp-and-antigravity-cli-83n</guid>
      <description>&lt;p&gt;This article provides a step by step debugging guide for deploying Gemma 4 to a Google Cloud TPU system,. A suite of Python MCP tools is built to simplify management of the vLLM hosted Gemma 4 deployment with Antigravity CLI.&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.amazonaws.com%2Fuploads%2Farticles%2Fzv8h3y1j9b76tt1ozt6n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzv8h3y1j9b76tt1ozt6n.jpeg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What is this project trying to Do?
&lt;/h4&gt;

&lt;p&gt;This project is a DevOps/SRE assistant that uses a Gemma 4 model hosted on TPU. It provides tools to provision the Docker container and deploy the model, as well as for observability and performance testing.&lt;/p&gt;

&lt;p&gt;This project is similar to a previous project that targeted GPU hosted Gemma4 instances on GCP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.clauneck.workers.dev/gde/gemma-sre-self-hosted-vllm-infrastructure-agent-2bam"&gt;Gemma-SRE: Self-Hosted vLLM Infrastructure Agent&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.&lt;/p&gt;

&lt;p&gt;Full details on installing Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.clauneck.workers.dev/gde/getting-started-with-antigravity-cli-183g"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing the Antigravity CLI Environment
&lt;/h4&gt;

&lt;p&gt;Once you have all the tools in place- you can test the startup of Antigravity CLI.&lt;/p&gt;

&lt;p&gt;You will need to authenticate with a Google Cloud Project or your Google Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the interface:&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.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mvg4a6tnyt4j8666lb1.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Full Installation Instructions
&lt;/h4&gt;

&lt;p&gt;The detailed installation instructions for Antigravity CLI are here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.clauneck.workers.dev/gde/getting-started-with-antigravity-cli-183g"&gt;Getting Started with Antigravity CLI&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Python MCP Documentation
&lt;/h4&gt;

&lt;p&gt;The official GitHub Repo provides samples and documentation for getting started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;GitHub - modelcontextprotocol/python-sdk: The official Python SDK for Model Context Protocol servers and clients&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Where do I start?
&lt;/h4&gt;

&lt;p&gt;The strategy for starting MCP development for model management is a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;First, the basic development environment is setup with the required system variables, and a working Antigravity CLI configuration.&lt;/p&gt;

&lt;p&gt;Then, a minimal Python MCP Server is built with stdio transport. This server is validated with Antigravity CLI in the local environment.&lt;/p&gt;

&lt;p&gt;This setup validates the connection from Antigravity CLI to the local server via MCP. The MCP client (Antigravity CLI) and the Python MCP server both run in the same local environment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup the Basic Environment
&lt;/h4&gt;

&lt;p&gt;At this point you should have a working Python environment and a working Antigravity CLI installation. The next step is to clone the GitHub samples repository with support scripts:&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="nb"&gt;cd&lt;/span&gt; ~
git clone https://github.com/xbill9/gemma4-tips
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;init.sh&lt;/strong&gt; from the cloned directory.&lt;/p&gt;

&lt;p&gt;The script will attempt to determine your shell environment and set the correct variables:&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="nb"&gt;cd &lt;/span&gt;tpu-12B-v6e1-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;init.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your session times out or you need to re-authenticate- you can run the &lt;strong&gt;set_env.sh&lt;/strong&gt; script to reset your environment variables:&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="nb"&gt;cd &lt;/span&gt;tpu-12B-v6e1-devops-agent
&lt;span class="nb"&gt;source &lt;/span&gt;set_env.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables like PROJECT_ID need to be setup for use in the various build scripts- so the set_env script can be used to reset the environment if you time-out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Model Management Tool with MCP Stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the standard MCP libraries provide is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level MCP tool implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;The connection over stdio will look similar to this:&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;# Initialize FastMCP server
&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Self-Hosted vLLM DevOps Agent&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;h4&gt;
  
  
  Running the Python Code
&lt;/h4&gt;

&lt;p&gt;First- switch the directory with the Python version of the MCP sample code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/tpu-12B-v6e1-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;install
&lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project can also be linted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/tpu-12B-v6e1-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make lint
ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
All checks passed!
ruff format &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
6 files already formatted
mypy &lt;span class="nb"&gt;.&lt;/span&gt;
Success: no issues found &lt;span class="k"&gt;in &lt;/span&gt;6 &lt;span class="nb"&gt;source &lt;/span&gt;files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a test run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/tpu-12B-v6e1-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;test
&lt;/span&gt;python test_agent.py
......2026-06-15 11:06:05,954 - vllm-devops-agent - INFO - Querying model with stats with prompt: &lt;span class="s1"&gt;'Hi...'&lt;/span&gt;
2026-06-15 11:06:05,954 - vllm-devops-agent - ERROR - Error fetching active model name from vLLM: object MagicMock can&lt;span class="s1"&gt;'t be used in '&lt;/span&gt;await&lt;span class="s1"&gt;' expression
2026-06-15 11:06:05,955 - vllm-devops-agent - INFO - Model response with stats: TTFT=0.000s, TotalTime=0.000s
..2026-06-15 11:06:05,960 - vllm-devops-agent - INFO - Starting TPU VM node node-1 in us-east5-a...
.2026-06-15 11:06:05,961 - vllm-devops-agent - INFO - Getting status for TPU VM node node-1 in us-east5-a...
.2026-06-15 11:06:05,962 - vllm-devops-agent - INFO - Stopping TPU VM node node-1 in us-east5-a...
.2026-06-15 11:06:05,964 - vllm-devops-agent - ERROR - Error fetching active model name from vLLM: object MagicMock can'&lt;/span&gt;t be used &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s1"&gt;'await'&lt;/span&gt; expression
&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nt"&gt;----------------------------------------------------------------------&lt;/span&gt;
Ran 12 tests &lt;span class="k"&gt;in &lt;/span&gt;0.069s

OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MCP stdio Transport
&lt;/h4&gt;

&lt;p&gt;One of the key features that the MCP protocol provides is abstracting various transport methods.&lt;/p&gt;

&lt;p&gt;The high level tool MCP implementation is the same no matter what low level transport channel/method that the MCP Client uses to connect to a MCP Server.&lt;/p&gt;

&lt;p&gt;The simplest transport that the SDK supports is the stdio (stdio/stdout) transport — which connects a locally running process. Both the MCP client and MCP Server must be running in the same environment.&lt;/p&gt;

&lt;p&gt;In this project Antigravity CLI is used as the MCP client to interact with the Python MCP server code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Antigravity CLI mcp_config.json
&lt;/h4&gt;

&lt;p&gt;A sample MCP server file is provided in the .agents directory:&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tpu-v6e1-12B"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/home/xbill/gemma4-tips/tpu-12B-v6e1-devops-agent/server.py"&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;"env"&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;"GOOGLE_CLOUD_PROJECT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aisprint-491218"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ACCELERATOR_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;"v6e-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;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;h4&gt;
  
  
  Validation with Antigravity CLI
&lt;/h4&gt;

&lt;p&gt;The final connection test uses Antigravity CLI as a MCP client with the Python code providing the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
&amp;gt; ✓ google-dev-knowledge Tools: search_documents, answer_query, get_documents
   ✓ tpu-v6e1-12B Tools: verify_model_health, save_hf_token, get_vllm_deployment_config,
                   get_vllm_tpu_deployment_config, destroy_queued_resource, +25 more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Model Lifecycle Management via MCP
&lt;/h4&gt;

&lt;p&gt;The MCP tools provide a complete suite of agent-oriented operations for managing vLLM deployment on Cloud Run or a TPU.&lt;/p&gt;

&lt;p&gt;Overview of MCP tools :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xbill@penguin:~/gemma4-tips/tpu-12B-v6e1-devops-agent&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;help
&lt;/span&gt;Available MCP tools &lt;span class="k"&gt;in &lt;/span&gt;server.py:

  get_vllm_deployment_config - Generates the gcloud &lt;span class="nb"&gt;command &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;a single-host TPU v6e vLLM deployment.
  get_vllm_tpu_deployment_config - Generates a GKE manifest &lt;span class="k"&gt;for &lt;/span&gt;a TPU v6e vLLM deployment.
  list_queued_resources - Lists all Queued Resources &lt;span class="k"&gt;in &lt;/span&gt;a specific zone.
  describe_queued_resource - Provides detailed information about a specific Queued Resource.
  get_reservation_status - Checks the lifecycle state and expiry &lt;span class="nb"&gt;time &lt;/span&gt;of a Queued Resource.
  check_tpu_availability - Simple check to see &lt;span class="k"&gt;if &lt;/span&gt;a Queued Resource has reached ACTIVE state.
  estimate_deployment_cost - Estimates the cost of a TPU deployment.
  get_system_status - Status dashboard prioritizing Queued Resource states &lt;span class="k"&gt;in &lt;/span&gt;us-central1-a.
  orchestrate_gemma4_stack - Seamless turnkey deployment of the Gemma 4 stack.
  get_vllm_endpoint - Discovery tool to verify connectivity and &lt;span class="k"&gt;return &lt;/span&gt;the active vLLM service URL.
  deploy_queued_vllm - Deploys vLLM strictly using Queued Resources &lt;span class="k"&gt;for &lt;/span&gt;Flex-start allocation.
  create_tpu_queued_resource - Creates a TPU Queued Resource &lt;span class="o"&gt;(&lt;/span&gt;Flex-start&lt;span class="o"&gt;)&lt;/span&gt; with the specified configuration.
  check_tpu_utilization - Monitors Tensor Core and HBM pressure on the TPU VM.
  get_vllm_metrics - Fetches real-time Prometheus metrics from the active vLLM service.
  validate_gemma4_deployment - Performs a comprehensive sanity check on the Gemma 4 deployment.
  query_queued_gemma4 - Queries the model hosted on the active Queued Resource.
  run_vllm_benchmark - Runs vLLM&lt;span class="s1"&gt;'s internal benchmark tool inside the container on the TPU VM.
  run_vllm_internal_benchmark - Runs vLLM'&lt;/span&gt;s internal benchmark tool inside the container on the TPU VM.
  run_external_load_test - Performs an external load &lt;span class="nb"&gt;test &lt;/span&gt;against the active vLLM endpoint.
  get_gemma4_full_report - Generates a deep technical report of the Gemma 4 deployment.
  fetch_tpu_vm_logs - Fetches specific logs from a TPU VM.
  grep_tpu_logs - Searches &lt;span class="k"&gt;for &lt;/span&gt;a pattern &lt;span class="k"&gt;in &lt;/span&gt;both startup and container logs on the TPU VM.
  destroy_queued_resource - Safely deletes a Queued Resource and its associated node.
  fetch_queued_node_logs - Fetches logs by identifying the node linked to a Queued Resource.
  save_hf_token - Saves HF token to GCP Secret Manager &lt;span class="k"&gt;for &lt;/span&gt;the Queued Resource deployer.
  analyze_cloud_logging - Searches Cloud Logging &lt;span class="k"&gt;for &lt;/span&gt;TPU-related errors and lifecycle events.
  verify_model_health - Performs a deep health check by querying the model with a simple prompt.
  query_vllm_with_metrics - Queries the model and provides streaming-based performance metrics.
  get_vllm_model_stats - Aggregates model-specific statistics from the vLLM server.
  find_tpu - Finds a zone with available quota and attempts to create the TPU queued resource.
  start_v6e1 - Starts a TPU v6e-1 instance.
  stop_v6e1 - Stops a TPU v6e-1 instance.
  status_v6e1 - Retrieves status and configuration details &lt;span class="k"&gt;for &lt;/span&gt;the TPU VM node.
  get_help - Provides &lt;span class="nb"&gt;help &lt;/span&gt;text and summarizes the configuration options and tools.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting Started with Gemma 4 on TPU
&lt;/h4&gt;

&lt;p&gt;The Official vLLM repo also has Gemma4 specific information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm/releases" rel="noopener noreferrer"&gt;Releases · vllm-project/vllm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Gemma 12B model was just released:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/introducing-gemma-4-12b/" rel="noopener noreferrer"&gt;Introducing Gemma 4 12B: a unified, encoder-free multimodal model&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Find and Start the TPU
&lt;/h4&gt;

&lt;p&gt;First check for available zones with quota:&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="gt"&gt;&amp;gt; get_zones_with_available_quota  &lt;/span&gt;

&lt;span class="gu"&gt;### 📊 Available Zones with Quota for TPUV6EPerProjectPerZoneForTPUAPI &lt;/span&gt;

  Quota: TPU v6e cores per project per zone ( TPUV6EPerProjectPerZoneForTPUAPI )

  • asia-east1-a, asia-east1-b, asia-east1-c: Limit = 512 
  • asia-northeast1-a, asia-northeast1-b, asia-northeast1-c: Limit = 512 
  • asia-south1-a, asia-south1-b, asia-south1-c: Limit = 512 
  • asia-southeast1-a, asia-southeast1-b, asia-southeast1-c: Limit = 512 
  • europe-west4-a, europe-west4-b, europe-west4-c: Limit = 512 
  • southamerica-east1-a, southamerica-east1-b, southamerica-east1-c: Limit = 512 
  • southamerica-west1-a, southamerica-west1-b, southamerica-west1-c: Limit = 512 
  • us-central1-a, us-central1-b, us-central1-c, us-central1-f: Limit = 512 
  • us-east1-b, us-east1-c, us-east1-d: Limit = 512 
  • us-east4-c: Limit = 512 
  • us-east5-a, us-east5-b: Limit = 512 
  • us-south1-a, us-south1-b, us-south1-c: Limit = 512 
  • us-west1-a, us-west1-b, us-west1-c: Limit = 512 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now — search for a region with availability:&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="gt"&gt;&amp;gt; find_tpu&lt;/span&gt;

● ListDir(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B)
● Read(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B/find_tpu.json)
● tpu-v6e1-12B/find_tpu(Find TPU resource) (ctrl+o to expand)

  I have executed the find_tpu tool, which checked multiple zones for TPU availability and successfully initiated
  the TPU creation for node-1 in zone asia-northeast1-b .

  ### 📋 Execution Summary:

  • Target Resource ID: node-1 
  • Success Zone: asia-northeast1-b 
  • Creation Output: Primary resource node-1 creation initiated with startup script. 
  • Failed Zones Log:
      • asia-east1-a , asia-east1-b : Accelerator type v6e-1 not found.
      • asia-east1-c : Flex-start provisioning model not supported for v6e-1 .
      • asia-northeast1-a : Zone not supported.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check for the startup of the TPU:&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="gt"&gt;&amp;gt; status&lt;/span&gt;

● Read(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B/get_system_status.json)
● tpu-v6e1-12B/get_system_status(Get system status)
● Read(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B/list_queued_resources.json)
● tpu-v6e1-12B/list_queued_resources(List queued resources)
● Read(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B/status_v6e1.json)
● tpu-v6e1-12B/status_v6e1(Get TPU status)
● Read(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B/describe_queued_resource.json)
● tpu-v6e1-12B/describe_queued_resource(Describe queued resource)
● Read(/home/xbill/.gemini/antigravity-cli/brain/c...cb2f8/.system_generated/steps/26/output.txt) (ctrl+o to expand)

  The status of the TPU queued resource is as follows:

  • Resource Name: node-1 
  • Zone: asia-northeast1-b 
  • State: WAITING_FOR_RESOURCES 🌀 (Flex-start scheduler is currently provisioning the TPU v6e-1 resource)
  • Configuration:
      • Accelerator: v6e-1 
      • Runtime: v2-alpha-tpuv6e 
      • Model to serve: google/gemma-4-12B-it via vllm/vllm-tpu:nightly 

  Since we are utilizing Flex-start VMs, the scheduler can take some time to acquire the resource when demand is
  high. It will automatically start up and launch the vLLM Docker container once provisioned.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and check the queued resource:&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="gt"&gt;&amp;gt; list_queued_resources&lt;/span&gt;

● tpu-v6e1-12B/list_queued_resources(List queued resources)
● tpu-v6e1-12B/list_queued_resources(List queued resources) (ctrl+o to expand)

  Here are the queued resources across the zones:

  ### 📋 Zone us-east5-a (Default)

  No queued resources found.

  ### 📋 Zone asia-northeast1-b &lt;span class="sb"&gt;

    NAME STATE NODE_ID ACCELERATOR_TYPE CREATE_TIME
    node-1 WAITING_FOR_RESOURCES 2026-06-15T15:55:01Z

&lt;/span&gt;&lt;span class="gt"&gt;&amp;gt; describe_queued_resources&lt;/span&gt;

● tpu-v6e1-12B/describe_queued_resource(Describe queued resource)
● Read(/home/xbill/.gemini/antigravity-cli/brain/c...cb2f8/.system_generated/steps/44/output.txt) (ctrl+o to expand)

  Here are the details for the queued resource node-1 in zone asia-northeast1-b :

  • Resource Name: node-1 (Node ID: node-1-node )
  • State: WAITING_FOR_RESOURCES 
  • Provisioning Model: FLEX_START 
  • Accelerator Configuration: v6e-1 
  • Runtime Version: v2-alpha-tpuv6e 
  • Valid Until Time (Queue Expiry): 2026-06-15T19:55:01Z 
  • Startup Script: Configured to pull the vllm/vllm-tpu:nightly Docker container, serve google/gemma-4-12B-it ,
  and check for the Application startup complete message in container logs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and check for the TPU:&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="gt"&gt;&amp;gt; check_tpu_availability&lt;/span&gt;

● Read(/home/xbill/.gemini/antigravity-cli/mcp/tpu-v6e1-12B/check_tpu_availability.json)
● tpu-v6e1-12B/check_tpu_availability(Check TPU availability) (ctrl+o to expand)

  The TPU check has completed:

  • Resource ID: node-1 ( asia-northeast1-b )
  • State: WAITING_FOR_RESOURCES 
  • Available: ⏳ No (The TPU queued resource is still waiting in queue for allocation).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  No TPU for you!
&lt;/h4&gt;

&lt;p&gt;The strategy was refined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;  ### 🛠️ What is happening under the hood:
&lt;span class="p"&gt;
  1.&lt;/span&gt; Added automatic status updates: When any zone fails creation, find_tpu immediately writes the exact failure
  reason back to tpu_zones_status.md and marks the zone as unavailable ( No ). (I verified this: asia-northeast1-
  c has already been updated with No and the exact error message in the status file!).
&lt;span class="p"&gt;  2.&lt;/span&gt; Added 3-Minute Active State Polling: For any zone where creation succeeds (like europe-west4-a did
  previously), the agent now polls the state every 15 seconds. If it fails to reach the ACTIVE state within 3
  minutes, the agent will:
      • Terminate the queued resource reservation.
      • Mark the zone as unavailable ( No ) in tpu_zones_status.md (with a timeout reason).
      • Automatically fall back and proceed to try the next zone in the quota list.

  I am monitoring this background search task and will report back as soon as it completes or succeeds in a zone.

● ManageTask(Task: cd3d1bd3-f48e-4200-a128-45fe431cb2f8/task-258)
○ Schedule() (ctrl+o to expand)

  I have scheduled a timer to check the task status again in 1 minute. It is currently evaluating the asia-south1-
  b zone. I will update you once we have more progress.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  We have Lift Off!
&lt;/h4&gt;

&lt;p&gt;Finally- a zone with availability and Quota as found!&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="gt"&gt;&amp;gt; status&lt;/span&gt;

● ManageTask(Task: cd3d1bd3-f48e-4200-a128-45fe431cb2f8/task-258) (ctrl+o to expand)
  The search task is currently running the 3-minute check for zone europe-west4-a :

  • Target Zone: europe-west4-a 
  • Resource: node-1 
  • Status: WAITING_FOR_RESOURCES ⏳ (Checking again every 15 seconds)

  If it stays in this state for the full 3 minutes, the script will automatically clean up node-1 in this zone,
  mark europe-west4-a as failed/unavailable in the status file, and try the next zone ( europe-west4-b ).

────────────────────────────────────────────────────────────
&lt;span class="gt"&gt;&amp;gt; status&lt;/span&gt;

● ManageTask(Task: cd3d1bd3-f48e-4200-a128-45fe431cb2f8/task-258) (ctrl+o to expand)

  The search has made critical progress:

  • Target Zone: europe-west4-a 
  • Resource State: PROVISIONING 🚀

  The Flex-start scheduler successfully allocated the v6e-1 TPU chip, and Google Cloud is currently provisioning
  the TPU VM node. This means the resource is secured and will transition to ACTIVE shortly!

● ManageTask(Task: cd3d1bd3-f48e-4200-a128-45fe431cb2f8/task-258)
● Schedule() (ctrl+o to expand)

  The TPU is still in the PROVISIONING phase. I have scheduled a 30-second timer to check again.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
&lt;span class="gt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally- get the TPU ACTIVE:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;  ### 🎉 Success! The TPU is ACTIVE in europe-west4-a 

  The search completed successfully and reached the ACTIVE state!

  #### 📊 Execution Summary
&lt;span class="p"&gt;
  1.&lt;/span&gt; Dynamic Timeout Extension:
      • As requested, the timeout was successfully extended to 10 minutes (600 seconds) as soon as the state
      reached PROVISIONING in europe-west4-a .
      • Log entry confirmation: Resource is PROVISIONING. Extending timeout to 10 minutes (600 seconds) from start.
&lt;span class="p"&gt;  2.&lt;/span&gt; ACTIVE State Reached:
      • The TPU VM node fully provisioned and reached ACTIVE shortly after ( Queued resource node-1 state in     
      europe-west4-a: ACTIVE ).
&lt;span class="p"&gt;  3.&lt;/span&gt; Status File Updated:
      • The tpu_zones_status.md status file has been updated to reflect the successful startup:
          • Successful Zone: europe-west4-a (Started, reached ACTIVE)
          • Table Entry: | &lt;span class="gs"&gt;**europe-west4-a**&lt;/span&gt; | Yes | Yes | Successfully started and reached ACTIVE state. | 
          • Previous failures for intermediate zones ( asia-northeast1-c , asia-south1-a , asia-south1-b , asia-
          south1-c , asia-southeast1-a , asia-southeast1-b , asia-southeast1-c ) were also successfully logged
          into the file during the search.

  The background timer check completed. Since the TPU has already reached the ACTIVE state in europe-west4-a 
  and the search task has finished successfully, no further actions are required.

  The TPU VM is now ready and active in europe-west4-a .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Review The Standard 12B Model
&lt;/h4&gt;

&lt;p&gt;The MCP server tools are used to deploy the model:&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="gt"&gt;&amp;gt; what is the memory breakdown of the 32 GB&lt;/span&gt;
  Based on the startup logs for the vllm-gemma4 JAX/TPU engine on the TPU v6e-1 (32 GB), here is the memory
  breakdown:

  ### 1. JAX Global Memory Reservation

  • Total Physical TPU Memory: 32.00 GB 
  • JAX-reserved Budget: 31.25 GB (95% of physical memory, configured via JAX_TPU_MEM_FRACTION=0.95 ). JAX
  claims this partition immediately upon container startup.
  • Host OS Overhead: ~ 0.75 GB (the remaining 5% of physical memory left for host processes and VM operations).
  ──────
  ### 2. TPU HBM (High Bandwidth Memory) Allocation

  Out of the 31.25 GB reserved for JAX/vLLM:

  • Model Weights (google/gemma-4-12B-it): 22.28 GB 
      • The 12B parameters of Gemma 4 are loaded in bfloat16 precision, occupying 22.28 GiB of memory.
  • KV Cache Allocation: 4.30 GB 
      • Configured dynamically in the remaining available memory.
      • Because it uses float8_e4m3fn (FP8) cache precision (which reduces memory footprint by 50% compared to   
      bfloat16 ), vLLM is able to fit 372 blocks across all 48 attention layers.
  • Activations &amp;amp; Scratchpad Buffers: 4.67 GB 
      • Used for intermediate activations, workspace memory for attention kernels, sharding management, and
      compilation execution paths.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cross Check The Deployed Model
&lt;/h4&gt;

&lt;p&gt;Once the model starts - the MCP tool allows for model verification:&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="gt"&gt;&amp;gt; verify_model_health&lt;/span&gt;

▸ Thought for 2s, 420 tokens
  Querying Active Model

● tpu-v6e1-12B/verify_model_health(Verify model health) (ctrl+o to expand)

  ### ✅ Model Health Check: PASSED

  • Status: Online and fully functional.
  • Model: google/gemma-4-12B-it 
  • Response: "Yes, I am working and ready to help!"
  • Inference Latency: 0.64 seconds 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and model details:&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="gt"&gt;&amp;gt; get_active_models&lt;/span&gt;

● tpu-v6e1-12B/get_active_models(Get active models)
● Read(/home/xbill/gemma4-tips/tpu-12B-v6e1-devops-agent/server.py)
● tpu-v6e1-12B/get_model_details(Get model details) (ctrl+o to expand)

  The get_active_models tool is designed specifically for Ollama backend servers. Since we are serving via vLLM
  on JAX/TPU, we can retrieve the active models and engine details by querying the vLLM API:

  ### 📊 Active Model (vLLM Engine)

  • Model ID: google/gemma-4-12B-it 
  • VLLM Engine Version: 0.22.1rc1.dev472+ga30addc75 (JAX/TPU optimized build)
  • Max Model Length: 4096 tokens
  • Service Status: Healthy ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the endpoint:&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="gt"&gt;&amp;gt; get_endpoint&lt;/span&gt;

● tpu-v6e1-12B/get_vllm_endpoint(Get vllm endpoint) (ctrl+o to expand)

  The active service endpoint for the TPU-hosted vLLM deployment is:

  • Endpoint URL: http://34.176.68.248:8000 
  • Status: 🟢 Online and healthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and status:&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="gt"&gt;&amp;gt; get_system_details&lt;/span&gt;

● Bash(gcloud compute tpus tpu-vm ssh node-1-node --zone=southamerica-west1-a --project=aisprint-...) (ctrl+o to
expand)

  I am running a diagnostic command via SSH to retrieve the hardware and system details of the TPU VM. I will
  present the system specifications once the command completes.

  Here is the system hardware and memory breakdown of the active TPU VM host ( node-1-node in southamerica-west1-
  a ):

  ### 🖥️ Hardware Specifications

  #### 1. CPU Specifications

  • CPU Model: AMD EPYC 9B14 (high-performance processor hosting the TPU driver runtime).

  #### 2. System Memory (RAM)

  • Total RAM: 172 GB 
  • Used RAM: 33 GB 
  • Free/Available RAM: 114 GB 

  #### 3. Shared Memory ( /dev/shm tmpfs)

  • Total Size: 87 GB 
  • Used Space: 24 GB (used for caching Hugging Face checkpoints to enable high-speed model loading).
  • Available Space: 64 GB 

  #### 4. TPU Accelerator details

  • Accelerator Type: Cloud TPU v6e-1 (Trillium architecture)
  • TPU Accelerator HBM: 32 GB (utilizing float8_e4m3fn quantization for KV Cache to optimize block sizes).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Check the Cloud TPU
&lt;/h4&gt;

&lt;p&gt;Once Deployed — the TPU is on the Google Cloud Console:&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.amazonaws.com%2Fuploads%2Farticles%2F1dmm9kujo6dt87gsbyfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dmm9kujo6dt87gsbyfu.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the logs:&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.amazonaws.com%2Fuploads%2Farticles%2F5wygqhrsyv2w81bk3dl7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wygqhrsyv2w81bk3dl7.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Review the Model
&lt;/h4&gt;

&lt;p&gt;Check the model details:&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="gt"&gt;&amp;gt; get_metrics&lt;/span&gt;

● tpu-v6e1-12B/get_metrics(Get metrics)
● Read(/home/xbill/.gemini/antigravity-cli/brain/c...7458/.system_generated/steps/341/output.txt) (ctrl+o to expand)

  Here is a summary of the active vLLM engine Prometheus metrics retrieved from the server:

  ### 📊 Engine Status

  • Engine Sleep State: Awake (value: 1.0 )
  • Running Requests: 0 
  • Waiting Requests: 0 

  ### 💾 Memory &amp;amp; Cache Usage

  • Process Resident Memory (RSS): 2.53 GB (RAM used by the host Python process running vLLM).
  • KV Cache Usage: 0.0% (no prompts currently occupying context slots).
  • Prefix Cache Queries: 20.0 (indicates token verification prompts processed).
  • Prefix Cache Hits: 0.0 (0% hit rate, normal since no repeated prompts have run).

  The full list of metrics (including GC collection counters and virtual memory details) has been stored
  successfully. If you run a benchmark or send prompts, these metrics will dynamically update!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is online — you can send queries to the Model:&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="gt"&gt;&amp;gt; query_gemma4_with_stats what is Gemma?&lt;/span&gt;

● tpu-v6e1-12B/query_queued_gemma4_with_stats(Query Gemma 4 with stats) (ctrl+o to expand)

  ### 📊 Performance Stats

  • Time to First Token (TTFT): 0.183 seconds (highly optimized JAX path!)
  • Output Speed: 46.93 tokens/s 
  • Total Generation Time: 9.963 seconds 
  • Total Tokens Generated: 459 
  ──────
  ### 💬 Model Response

  Depending on the context, "Gemma" usually refers to one of three things: a popular AI model family, a gemstone,
  or a name.

  Here is a breakdown of the most common meanings:

  #### 1. In Artificial Intelligence (Google's Gemma)

  If you are asking in the context of technology or AI, Gemma is a family of lightweight, state-of-the-art open    
  models developed by Google DeepMind.

  • Relationship to Gemini: Gemma is built from the same research and technology used to create the larger Gemini
  models.
  • "Open" Nature: Unlike Gemini (which is mostly accessed via API or private cloud), Gemma is "open," meaning
  developers can download the weights, customize them, and run them on their own hardware (like a personal laptop
  or a private server).
  • Size: It is designed to be smaller and more efficient than massive frontier models, making it ideal for
  developers who want to build specific applications, research new techniques, or run AI locally.
  • Versions: There are different sizes (e.g., 2B, 7B, 9B, 27B parameters) and versions (like Gemma 1, Gemma 2, and
  variants like "it" for instruction-tuned).

  #### 2. In Gemology (The Gemstone)

  Gemma is the Latin word for "gem" or "precious stone."

  • In jewelry and mineralogy, it refers to a precious stone that has been cut and polished.
  • It is often used in historical contexts to describe high-quality jewels.

  #### 3. As a Name

  Gemma is a popular female given name, primarily in European cultures (especially Italy and the UK).

  • Meaning: It is derived from the Latin word for "gem" or "precious stone."
  • Variations: It is sometimes used as a nickname for "Germaine" or "Georgia."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Benchmark the Local Model
&lt;/h4&gt;

&lt;p&gt;The local model was benchmarked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The benchmark sweep has completed successfully.

  Here is a quick scorecard of the benchmark results:

  • Max Speed (8 to 512 Tokens): ~63 req/s (Optimal performance reached at ≥128 concurrent users).
  • High-Context Speed (4096 Tokens): ~41 req/s (Maintains 65% of max speed despite the massive input size).
  • Speed per User:
      • Single user: ~0.5s response time.
      • 2,048 concurrent users: ~1.2s to 1.9s response time (high efficiency under load).
  • Physical Limit: 4,096 tokens is the maximum stable context window. Attempting 8,192 or 16,000 tokens results in
  0% success due to the TPU's physical memory limit (32 GB).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is visualized:&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.amazonaws.com%2Fuploads%2Farticles%2F902yss87poxx3la579jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F902yss87poxx3la579jj.png" width="800" height="560"&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.amazonaws.com%2Fuploads%2Farticles%2Ftv0olzlwv6u1eluw0vbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv0olzlwv6u1eluw0vbn.png" width="800" height="560"&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.amazonaws.com%2Fuploads%2Farticles%2Fz9heqte3gylh2kt45tq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9heqte3gylh2kt45tq6.png" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Comparison with Other Deployments
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;  Comparing Hardware Architectures
  Here is a direct comparison between the performance of the Gemma 4 12B model on your TPU v6e-1 (32 GB) deployment
  versus the NVIDIA L4 GPU (24 GB) QCE deployment using the Quantization-Aware Training (QAT INT4) model.

  ### ⚙️ Deployment Configurations

  • TPU v6e-1 Node: Served the standard Gemma 4 12B IT (FP8/BF16). Model weights consume ~22 GB, leaving less RAM
  for KV cache.
  • NVIDIA L4 GPU Node: Served Gemma 4 12B QAT (INT4 quantized). Quantization compresses the model weights to ~6 GB,
  leaving ~18 GB of VRAM free for the KV cache.
  ──────
  ### 📊 Head-to-Head Performance Comparison

   Metric | TPU v6e-1 (Stand… | NVIDIA L4 GPU (QAT … | Winner
  ---------------------------------|-------------------|----------------------|------------------------------------
   Max Success Context Window | 4,096 tokens (8K | 16,384 tokens | 🏆 GPU L4 (QAT)
                                   | &amp;amp; 16K failed | (Quantized model |
                                   | completely due to | size frees up VRAM |
                                   | memory limits). | for massive KV |
                                   | | cache). |
   Peak Throughput (Short Context) | ~63 req/s | ~40 req/s | 🏆 TPU v6e-1 (1.5x faster)
   Peak Throughput (4,096 Context) | ~41 req/s | ~16 req/s | 🏆 TPU v6e-1 (2.5x faster)
   Low-load Latency (1 User) | ~0.53s | ~0.15s | 🏆 GPU L4 (Faster single-user
                                   | | | response)
   High-load Latency (512 Users) | ~1.23s | ~7.61s - 11.26s | 🏆 TPU v6e-1 (9x faster under
                                   | | | load)
   Massive Concurrency Scaling | 100% Success Rate | Success drops to | 🏆 TPU v6e-1
                                   | up to 2,048 users | ~70% at 2,048 users; |
                                   | for ≤4 K context. | queue latencies |
                                   | | exceed 30 seconds. |
  ──────
  ### 💡 Key Takeaways
&lt;span class="p"&gt;
  1.&lt;/span&gt; The Memory vs. Quantization Trade-off:
  The NVIDIA L4 GPU wins on maximum context size (16K vs 4K) strictly because it runs a highly quantized INT4 model
  that fits in a small memory footprint. If the TPU deployment were switched to a quantized model, it would
  similarly unlock the 8K/16K boundaries.
&lt;span class="p"&gt;  2.&lt;/span&gt; Compute Power &amp;amp; Concurrency Scaling:
  The TPU v6e-1 is a compute powerhouse. Under concurrent user loads (≥32 users), the TPU maintains sub-2-second
  response times and processes up to 63 requests per second, whereas the L4 GPU quickly forms a bottleneck queue,
  dragging average latency out to over 10–30 seconds.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare the models to Cloud Run:&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="gt"&gt;&amp;gt; compare this to Cloud Run ../gpu-12B-qat-L4-devops-agent&lt;/span&gt;

● ListDir(/home/xbill/gemma4-tips/gpu-12B-qat-L4-devops-agent)
● Read(/home/xbill/gemma4-tips/gpu-12B-qat-L4-devops-agent/benchmark_report_summary.md) (ctrl+o to expand)

▸ Thought for 3s, 752 tokens
  Verifying File Differences
  The Cloud Run GPU deployment ( ../gpu-12B-qat-L4-devops-agent ) runs the exact same hardware profile (a single
  NVIDIA L4 GPU with 24 GB VRAM) and the quantized model ( google/gemma-4-12B-it-qat-w4a16-ct ) as the G2 VM setup.

  When compared to your TPU v6e-1 deployment, the comparison remains the same:

  ### 📊 Performance Metrics Comparison

   Feature | TPU v6e-1 (Standard Model) | Cloud Run L4 GPU (QAT INT4 Model)
  -----------------------------------|-----------------------------------|-----------------------------------------
   Peak Throughput | 🏆 ~63 req/s | ~40 req/s (L4 GPU max compute ceiling)
   Peak 4K Throughput | 🏆 ~41 req/s | ~16 req/s
   Max Context Size | 4,096 tokens (FP8/BF16 size | 🏆 16,384 tokens (Enabled by INT4
                                     | constraint) | compression)
   Idle Latency (1 user) | ~0.53s | 🏆 ~0.14s
   Concurrency Latency (512 users) | 🏆 ~1.23s (Handles massive | ~7.61s (Bottlenecks on single GPU
                                     | concurrent queuing) | stream)
   Request Success Rate (2048 users) | 🏆 100% (for ≤4 K contexts) | ~70% (Queue timeouts &amp;amp; failures under
                                     | | load)

  ### 💡 Key Takeaway on Cloud Run vs. TPU

  While Cloud Run provides excellent serverless scale-to-zero capabilities and very low initial response times
  (~0.14s) for a single user, it struggles under heavy concurrent spikes on a single replica.

  The TPU v6e-1 acts as a dedicated high-capacity pipeline: it processes concurrent queues far more efficiently,
  maintaining 100% reliability and near-instant latencies where Cloud Run's single L4 instance queues start timing
  out (success drops to ~70% and latency spikes to 27s+).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about the AWS deployed model?&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="gt"&gt;&amp;gt; compare to /home/xbill/gemma4-tips-aws/gpu-12B-qat-L4-devops-agent&lt;/span&gt;

● ListDir(/home/xbill/gemma4-tips-aws/gpu-12B-qat-L4-devops-agent)
▸ Thought Process
● Read(/home/xbill/gemma4-tips-aws/gpu-12B-qat-L4-devops-agent/benchmark_report_summary.md) (ctrl+o to expand)
  Analyzing Performance Differences
  Here is the performance comparison between your TPU v6e-1 (32 GB) deployment and the AWS EC2 g6.2xlarge 
  deployment (1 x NVIDIA L4 GPU, 24 GB VRAM) running the quantized model.
  ### ⚙️ AWS Setup vs. TPU Setup

  • AWS EC2 ( g6.2xlarge ): Runs the Gemma 4 12B QAT (INT4 quantized) model. It also leverages vLLM's FP8 KV cache
  quantization.
  • TPU v6e-1: Runs the standard Gemma 4 12B IT (FP8/BF16) model with standard KV cache settings.
  ──────
  ### 📊 Performance Comparison Table

   Metric | TPU v6e-1 (Standa… | AWS EC2 L4 GPU (QAT INT4 + … | Winner
  ------------------------------|--------------------|------------------------------|------------------------------
   Max Context Size | 4,096 tokens | 🏆 16,384 tokens (INT4 model | AWS GPU (QAT)
                                | (8K/16K fail due | + FP8 KV cache frees up |
                                | to memory). | VRAM). |
   Ultra-Short Context Peak | ~63 req/s | 🏆 ~93 req/s (INT4 compute | AWS GPU (QAT)
   (4–8 tokens) | | is highly optimized for tiny |
                                | | contexts). |
   Standard Context Peak | 🏆 ~61 req/s | ~27 req/s | TPU v6e-1 (2.2x faster)
   (128–512 tokens) | | |
   Long Context Peak (4,096 | 🏆 ~41 req/s | ~25 req/s | TPU v6e-1 (1.6x faster)
   tokens) | | |
   Single-User Latency (1 User) | ~0.53s | 🏆 ~0.09s–0.13s (Faster | AWS GPU
                                | | initial response). |
   Heavy-Load Latency (512 | 🏆 ~1.23s | ~3.03s–9.61s | TPU v6e-1 (2.5x to 8x
   Users) | | | faster)
   Extreme Concurrency Latency | 🏆 ~1.89s | ~32.02s (Server queuing | TPU v6e-1 (16x faster)
   (2,048 Users) | | delays start dominating). |
  ──────
  ### 💡 Key Takeaways
&lt;span class="p"&gt;
  1.&lt;/span&gt; Tiny Prompts vs. Real Workloads:
  The AWS L4 GPU hits a massive peak of 93 req/s for tiny prompts (4–8 tokens) because 4-bit weights and quantized
  KV caches make processing tiny commands extremely fast. However, as prompt sizes grow to realistic lengths (128+
  tokens), the TPU v6e-1's raw compute power takes over, processing requests 1.5x to 2.2x faster than the GPU.
&lt;span class="p"&gt;  2.&lt;/span&gt; Low-Load vs. High-Load Latency:
  The L4 GPU is extremely snappy for a single user (under 0.1s). However, under load, the GPU queuing begins
  bottlenecking immediately. The TPU v6e-1 maintains consistent sub-2-second latencies even under massive
  concurrency stress (2,048 concurrent users), whereas the AWS GPU latency shoots up past 30 seconds.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Cost Breakdown
&lt;/h4&gt;

&lt;p&gt;Finally — the last key question. What deployment is most cost effective?&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="gt"&gt;&amp;gt; compare costs across TPU, GCE with GPU, Cloud Run, and AWS EC2 GPU do not use MCP tool&lt;/span&gt;
▸ Thought Process
  Considering Pricing Alternatives
  Based on standard cloud pricing for Google Cloud (GCP) and AWS (specifically us-east-1 and us-central1 ), here
  is a comprehensive cost comparison across all four deployment configurations for serving the Gemma 4 12B model.
  ### 💵 Cost Breakdown Matrix

   Platform | Machine / Spec Profile | On-Deman… | Spot / P… | Monthly … | Cost per 100k Reque…
  ---------------------|--------------------------------|-----------|-----------|-----------|----------------------
   GCP Cloud TPU v6e-1 | 1 x TPU v6e Chip (32GB HBM2) | $1.20 | $0.75 | ~$876 | $0.53 (63 req/s)
   GCP GCE VM GPU | g2-standard-8 (8 vCPU, 32GB | $0.70 | $0.22 | ~$511 | $0.81 (24 req/s)
                       | RAM, 1 x L4) | | | |
   GCP Cloud Run GPU | Serverless (4 vCPU, 16GB RAM, | $0.95 | N/A | Variable | $1.10 (24 req/s)
                       | 1 x L4)&lt;span class="err"&gt;*&lt;/span&gt; | (Only | | (Scales |                      
                       | | when | | to $0) |
                       | | active) | | |
   AWS EC2 GPU | g6.2xlarge (8 vCPU, 32GB | $0.98 | $0.33 | ~$714 | $0.98 (28 req/s)
                       | RAM, 1 x L4) | | | |

  &lt;span class="err"&gt;*&lt;/span&gt;Note: Calculation based on standard benchmark throughput at 128-token context window.
  ──────
  ### 🔍 Key Cost/Performance Analysis

  #### 1. The Value Champion: GCP Cloud TPU v6e-1

  • Why: Although the hourly rate is the highest ($1.20/hr), the massive parallel processing power of the TPU makes
  it the most cost-efficient platform for high-traffic environments.
  • Efficiency: Because it handles 63 requests/sec (compared to 24–28 on an L4 GPU), it only costs $0.53 to process
  100,000 requests, which is nearly half the cost of the L4 GPU options.

  #### 2. The Idle Traffic Savior: GCP Cloud Run GPU

  • Why: If your application has intermittent traffic (e.g., used only during business hours or has long periods of
  silence), Cloud Run is the clear winner because it scales to zero replicas.
  • Efficiency: You pay $0.00 when idle, whereas virtual machine configurations (GCE/AWS/TPU) bill you 24/7
  regardless of whether a user is active.

  #### 3. GCP GCE VM vs. AWS EC2 L4 GPU

  • Why: Running a persistent single GPU VM is cheaper on Google Cloud than AWS. GCP GCE G2 VM configurations cost
  roughly $0.70/hr on-demand, whereas the equivalent AWS EC2 instance ( g6.2xlarge ) costs $0.98/hr (~40% more
  expensive on AWS).
  • Spot Instances: If you can tolerate instances being reclaimed (e.g., for background task workers), GCP GCE Spot
  L4 at $0.22/hr represents the absolute lowest baseline price to run a persistent VM.
  ──────
  ### 🏆 Recommendation Summary

  • Choose TPU v6e-1 if you have continuous, high-concurrency production traffic. The throughput-per-dollar
  efficiency is unbeatable.
  • Choose Cloud Run GPU if you are hosting a dev/staging setup or an application with unpredictable, low-density  
  traffic where scaling to zero saves 90%+ of costs.
  • Choose GCP GCE VM (Spot) if you need a cheap, persistent background worker for model processing.&lt;span class="err"&gt;*&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but why does the TPU get these numbers?&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="gt"&gt;&amp;gt; why does the TPU perform better in these benchmarks?&lt;/span&gt;

  If we look at the exact numbers from the data sheets, the TPU v6e wins the benchmark because of how vLLM         
  schedules concurrent requests on each piece of hardware.

  Here is what happens inside the server during the tests:

  ### 1. The L4 GPU hits the "Queuing Wall"

  When 512 or 2,048 users send requests to the NVIDIA L4 GPU, the GPU's memory bus (GDDR6 at 300 GB/s) and compute
  engine (121 TFLOPs) get fully saturated almost immediately.

  • To prevent crashing, vLLM is forced to queue the remaining users.
  • A user's request sits in a buffer waiting for its turn. By the time it is processed, 25 to 30 seconds have     
  passed. This is why the L4's throughput caps out early at 40 req/s, and its latencies spike.

  ### 2. The TPU v6e has a "Highway" (High Bandwidth Memory)

  Because the TPU v6e has 819 GB/s of HBM2 memory bandwidth, it can fetch the model weights and KV caches nearly 3x
  faster than the L4 GPU.

  • In LLM serving, the chip must read the entire 22 GB model from memory for every single token generated.
  • With 300 GB/s, the L4 GPU spends most of its time waiting for memory to arrive at the compute cores.
  • With 819 GB/s, the TPU streams these weights fast enough to keep the matrix multipliers fully fed, allowing it
  to process massive batches of concurrent requests simultaneously.
  ### 3. Google XLA Graph Compilation
  The TPU is powered by the XLA (Accelerated Linear Algebra) compiler.

  • Before the benchmark starts, XLA compiles the Gemma 4 model into optimized static execution paths on the
  hardware.
  • When vLLM batches 128 or 256 concurrent requests together, XLA executes them as a single, massive matrix
  operation. This allows the TPU to maintain ~1.2 seconds of average latency under heavy concurrency, whereas the
  GPU's dynamic kernel scheduling adds massive overhead under load.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and are you really really sure?&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="gt"&gt;&amp;gt; are you sure? you not making this up?&lt;/span&gt;

  Yes, I am absolutely sure, and these technical details are grounded in real hardware specifications and the
  architecture of LLM serving.

  Here is the verifiable data that proves these points:

  ### 1. Verifiable Hardware Specs (Public Specs)

  • NVIDIA L4 GPU specs: NVIDIA's official datasheet https://www.nvidia.com/en-us/data-center/l4/ shows the L4 is
  built on the Ada Lovelace architecture, has 24 GB GDDR6 memory, a memory bandwidth of 300 GB/s, and provides 121 
  TFLOPs of FP8 / Tensor Core compute.
  • TPU v6e specs: Google Cloud's official documentation https://cloud.google.com/tpu/docs/v6e lists the TPU v6e
  (Trillium) with 32 GB HBM2 memory, a memory bandwidth of 819 GB/s, and 225 TFLOPs of BF16 compute capacity per
  chip.

  ### 2. Verifiable LLM Serving Mechanics (The Math of LLMs)

  LLMs are famously memory-bandwidth bound during the token generation phase (decoding).

  • During decoding, to generate one token, the server has to load every single weight of the model from VRAM into
  the processor cores.
  • For a 12 Billion parameter model running in FP8 (where each parameter is 1 byte), the chip must transfer 12    
  Gigabytes of data per token generated.
  • The Math:
      • On an L4 GPU:&lt;span class="sb"&gt;

    300 GB/s                                                                                                      
    ───────── ≈ 25 tokens/sec                                                                                     
     12 GB                                                                                                        

&lt;/span&gt;  maximum theoretical speed for a single user (no batching overhead).

  • On a TPU v6e:&lt;span class="sb"&gt;

    819 GB/s
    ───────── ≈ 68 tokens/sec
     12 GB

&lt;/span&gt;  maximum theoretical speed.

  • When you add concurrent users (using vLLM's continuous batching to reuse weights), the TPU's 2.7x higher memory
  bandwidth allows it to scale throughput to much higher batch sizes before bottlenecking.

  ### 3. JAX/XLA Compilation

  If you check the startup logs of vLLM on a TPU VM (which you can do by running docker logs vllm-gemma4 on the
  node), you will see JAX spending 10 to 15 minutes at startup doing AOT (Ahead-of-Time) Graph Compilation. It
  compiles static execution graphs for specific batch sizes (e.g., batch sizes 1, 2, 4, 8, 16, 32, 64, 128, 256).
  This compile phase is unique to XLA/TPUs and is the reason the TPU can process large batched matrices with near-
  zero scheduling overhead compared to standard CUDA GPUs.

  So no, I am not making it up! The physics of memory transfer and the math of neural network execution dictate
  exactly why the TPU dominates the GPU under concurrent load.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The strategy for using MCP for Gemma 4 GPU deployment with Antigravity CLI was validated with a incremental step by step approach.&lt;/p&gt;

&lt;p&gt;A minimal stdio transport MCP Server was started from Python source code and validated with Antigravity CLI running as a MCP client in the same local environment. This Python server provided all of the management tools to deploy and troubleshoot TPU deployments.&lt;/p&gt;

</description>
      <category>antigravitycli</category>
      <category>gemma4</category>
      <category>mcps</category>
      <category>tpu</category>
    </item>
    <item>
      <title>Trust Begins with DNS: Mitigating Abuse and Strengthening Internet Resilience with Google Cloud</title>
      <dc:creator>Gbemisola Esho</dc:creator>
      <pubDate>Wed, 17 Jun 2026 04:11:30 +0000</pubDate>
      <link>https://dev.clauneck.workers.dev/gde/trust-begins-with-dns-mitigating-abuse-and-strengthening-internet-resilience-with-google-cloud-53n2</link>
      <guid>https://dev.clauneck.workers.dev/gde/trust-begins-with-dns-mitigating-abuse-and-strengthening-internet-resilience-with-google-cloud-53n2</guid>
      <description>&lt;p&gt;Tackling DNS abuse requires a blend of resilient infrastructure, traffic filtering, and advanced analytical reasoning. &lt;br&gt;
This because DNS is the internet’s address book, mitigating abuse means securing the records themselves, protecting the servers from being overwhelmed, and outsmarting the malicious actors who register deceptive domains.&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.amazonaws.com%2Fuploads%2Farticles%2Fuktth77bxp5olkm2moxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuktth77bxp5olkm2moxs.png" alt=" " width="800" height="640"&gt;&lt;/a&gt;&lt;br&gt;
Google Cloud Platform (GCP) addresses these different facets of DNS abuse through a combination of native infrastructure tools and advanced AI models. Here is how specific GCP tools map to the mitigation strategies:&lt;/p&gt;
&lt;h2&gt;
  
  
  Protocol-Level Defenses: Cloud DNS &amp;amp; DNSSEC
&lt;/h2&gt;

&lt;p&gt;To defend against cache poisoning and DNS spoofing, the integrity of the DNS records must be cryptographically guaranteed.&lt;br&gt;
The Tool: Google Cloud DNS&lt;br&gt;
How it Works: Cloud DNS is a resilient, low-latency, global DNS serving infrastructure. To specifically address spoofing, it offers managed DNSSEC (Domain Name System Security Extensions). Cloud DNS automates the management of cryptographic keys and the signing of zones. By enabling DNSSEC, you ensure that resolving nameservers can verify that the DNS responses haven't been tampered with in transit, neutralizing man-in-the-middle manipulation.&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.amazonaws.com%2Fuploads%2Farticles%2Faun2ux1bxj6fz1mosspg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faun2ux1bxj6fz1mosspg.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  DDoS &amp;amp; Amplification Mitigation: Google Cloud Armor
&lt;/h2&gt;

&lt;p&gt;When attackers use spoofed IP addresses to bounce massive DNS responses off open resolvers (DNS Amplification), the resulting volumetric DDoS attack can take down entire networks.&lt;br&gt;
The Tool: Google Cloud Armor&lt;br&gt;
How it Works: Sitting at the network edge, Cloud Armor provides always-on DDoS protection. It is built on the same infrastructure that protects Google Search and YouTube. Cloud Armor absorbs volumetric attacks, including DNS amplification floods, before they ever reach your backend infrastructure. By deploying adaptive protection and rate-limiting policies, it drops malicious, high-volume traffic while allowing legitimate user requests to pass through seamlessly.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://apinke.github.io/cloud-armor-visualizer/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;apinke.github.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
 The interactive visualization that demonstrates how Google Cloud Armor acts as a protective shield for your backend infrastructure. This simulation allows you to see volumetric attacks, such as DNS amplification floods, being absorbed in real-time by the global infrastructure shared with Google Search and YouTube.

&lt;p&gt;This visualization shows how incoming traffic (green particles) flows smoothly to your backend, while triggered attacks (red volumetric pulses or orange DNS amplification floods) are neutralized at the "Cloud Armor Edge" layer. This mimics the same scrubbing mechanism used by Google's global network.&lt;/p&gt;
&lt;h2&gt;
  
  
  Advanced Phishing &amp;amp; DGA Detection
&lt;/h2&gt;

&lt;p&gt;AI-Driven Analysis&lt;br&gt;
Standard infrastructure tools struggle with the rapid registration of fraudulent domains, typosquatting, and Domain Generation Algorithms (DGAs). Static blocklists are always a step behind attackers who register thousands of disposable domains a day.&lt;br&gt;
The Tool: The Gemma 4 Model&lt;br&gt;
How it Works: To proactively catch these threats, you can build a custom DNS Phishing Domain Analyser utilizing the advanced reasoning capabilities of the Gemma 4 model. Instead of relying on static lists, a Gemma-powered analyser can ingest domain data such as string entropy, lexical structure, registration timestamps, and WHOIS patterns and reason through the context to flag sophisticated evasion tactics. For example, the model can detect semantic anomalies or homograph attacks (like a Cyrillic 'а' replacing a Latin 'a') that bypass traditional security filters.&lt;br&gt;
By combining the edge protection of Cloud Armor, the cryptographic integrity of Cloud DNS, and the programmatic reasoning of Gemma 4, you create a comprehensive net against both infrastructure-level and application-level DNS abuse.&lt;/p&gt;

&lt;p&gt;This analyzer focuses on the infrastructure level specifically investigating DNS records, security protocols, and domain patterns to flag potential lookalike, typo-squatted, or malicious phishing domains targeting your infrastructure or brand.&lt;br&gt;
I have created a stand alone which is serverless that leverages DOH Serverless Heuristic &amp;amp; Infrastructure Profiling via DNS-over-HTTPS and the Custom which uses traditional DNS Heuristics with Gemma 3 Pro's high-level contextual awareness.&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://storage.googleapis.com/standalonedomainanalyzer/standindex.html" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;storage.googleapis.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://apinke.github.io/DNS-Phising-Analyzers/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;apinke.github.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>dns</category>
      <category>dnssec</category>
      <category>dnsabuse</category>
      <category>trustinthecloud</category>
    </item>
  </channel>
</rss>
