<?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: Suresh Kumar Pallapothu</title>
    <description>The latest articles on DEV Community by Suresh Kumar Pallapothu (@suresh_kumar_de3920bedd1c).</description>
    <link>https://dev.to/suresh_kumar_de3920bedd1c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155639%2Fba5bc6f9-6f9b-45f1-a010-b32ca0fc2751.png</url>
      <title>DEV Community: Suresh Kumar Pallapothu</title>
      <link>https://dev.to/suresh_kumar_de3920bedd1c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suresh_kumar_de3920bedd1c"/>
    <language>en</language>
    <item>
      <title>Day 4: Optimizing Model Weights — Quantization Basics (GGUF, AWQ) for Efficient Inference</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Tue, 10 Nov 2026 20:35:09 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-4-optimizing-model-weights-quantization-basics-gguf-awq-for-efficient-inference-3ebp</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-4-optimizing-model-weights-quantization-basics-gguf-awq-for-efficient-inference-3ebp</guid>
      <description>&lt;p&gt;Production AI runs into the &lt;strong&gt;memory wall&lt;/strong&gt;. LLMs are bound by memory bandwidth, because every generated token moves billions of parameters from memory to the compute cores. A standard 8-billion-parameter model in &lt;strong&gt;FP16&lt;/strong&gt; needs roughly &lt;strong&gt;16 GB&lt;/strong&gt; of VRAM for the weights alone, before the KV-cache and context. &lt;strong&gt;Quantization&lt;/strong&gt; compresses the weights so these models fit on commodity enterprise hardware without a collapse in quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bytes per parameter
&lt;/h2&gt;

&lt;p&gt;FP16 stores each weight in 16 bits: 1 sign bit, 5 exponent bits and 10 mantissa bits, which is 2 bytes. INT4 uses 4 bits, which is 0.5 bytes, a 75% saving.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 1 · Weights only, before KV-cache and runtime overhead. INT4 cuts weight memory by 75% compared with FP16. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-4-quantization" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The mathematics
&lt;/h2&gt;

&lt;p&gt;Quantization maps a float weight to a small integer with a scale and an offset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q = round(w / S) + Z
w ≈ S × (q − Z)        (de-quantize)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;w&lt;/code&gt; is the original weight, &lt;code&gt;S&lt;/code&gt; a scale factor, &lt;code&gt;Z&lt;/code&gt; a zero-point offset and &lt;code&gt;q&lt;/code&gt; the stored integer. At run time the weights are de-quantized on the fly, or used directly by low-bit kernels, which cuts memory by up to 75%.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 2 · Quantization maps many float values onto a small set of integer levels. The rounding error is the price paid for memory savings. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-4-quantization" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a tiny runnable demonstration of the rounding error you trade for that saving. It uses simple 4-bit quantization of a random weight vector, so it shows the principle rather than what production formats do (they quantize in small groups and keep extra scales to reduce the error).&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;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# pretend these are weights&lt;/span&gt;

&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;levels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;                                       &lt;span class="c1"&gt;# 15 steps for 4-bit&lt;/span&gt;
&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;min&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;                           &lt;span class="c1"&gt;# scale factor&lt;/span&gt;
&lt;span class="no"&gt;Z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;min&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="no"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                    &lt;span class="c1"&gt;# zero point&lt;/span&gt;

&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# quantize: stored as small integers&lt;/span&gt;
&lt;span class="n"&gt;w_hat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                         &lt;span class="c1"&gt;# de-quantize&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max level:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&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="s2"&gt;" memory: 16 bits -&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bits per weight"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mean absolute error:"&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="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w_hat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Choosing a format
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 3 · GGUF for portable CPU and mixed deployments, AWQ for high-throughput GPU serving, GPTQ for calibrated 4-bit GPU models. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-4-quantization" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GGUF&lt;/strong&gt;: the format used by llama.cpp and Ollama. It targets CPUs and Apple Silicon, supports offloading layers between CPU and GPU, and ships as one portable file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWQ&lt;/strong&gt; (Activation-aware Weight Quantization): suits high-throughput GPU serving through engines such as vLLM and TensorRT-LLM. It protects the small set of weights that matter most, and holds up well at 4-bit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPTQ&lt;/strong&gt;: a post-training method for GPUs that calibrates 4-bit weights against a sample dataset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The format has to match the runtime you plan to use, so decide the engine first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quality trade-off
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 4 · Lower bit widths shrink the file but cost quality. Sizes are approximate for an 8B model. Quality bars are illustrative, since real loss depends on the model and the task. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-4-quantization" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Q8_0&lt;/strong&gt; is very close to FP16 and halves the memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q4_K_M&lt;/strong&gt; is a common default for local deployment. An 8B model drops to about 4.8 GB and runs on a 16 GB workstation, usually with a small quality loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q2_K&lt;/strong&gt; compresses hardest and can show clear degradation, such as weaker reasoning and malformed JSON output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How much quality you lose depends on the model and the task, so run your own prompts and a few edge cases before you commit. For example, with Ollama you can pull a specific quantization by tag: &lt;code&gt;ollama pull llama3.1:8b-instruct-q4_K_M&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;This is the exact example from the video. A real script with a real result.&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;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;levels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;min&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="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;
&lt;span class="no"&gt;Z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;min&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="no"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="no"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="no"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;levels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w_hat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="no"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"levels used:"&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="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;max&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="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"mean abs error:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;round&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="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w_hat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"size in bytes:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;8&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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python quantize.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output I got while recording the video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;levels used: 16
mean abs error: 0.00604
size in bytes: 8192 -&amp;gt; 2048
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 5:&lt;/strong&gt; Local AI runtimes: deploying Ollama and vLLM for bare-metal execution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Quantization #MachineLearning #Optimization #LLMs #GGUF #vLLM #DataScience #AIEngineering
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-4-quantization" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/ai-int-day-4-quantization&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>quantization</category>
      <category>aiintermediateseries</category>
    </item>
    <item>
      <title>Day 3: Hardware Acceleration in Docker — GPU Passthrough and Limits for Constrained Hardware</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Mon, 09 Nov 2026 23:08:11 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-3-hardware-acceleration-in-docker-gpu-passthrough-and-limits-for-constrained-hardware-b75</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-3-hardware-acceleration-in-docker-gpu-passthrough-and-limits-for-constrained-hardware-b75</guid>
      <description>&lt;p&gt;A leading reason AI workloads fail in containers is the boundary between the container's isolated namespace and the host's hardware. Running inference on raw CPUs creates big latency bottlenecks, and exposing a GPU or accelerator takes deliberate host setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The acceleration architecture
&lt;/h2&gt;

&lt;p&gt;A container cannot talk to a PCIe device without driver support. The &lt;strong&gt;NVIDIA Container Toolkit&lt;/strong&gt; supplies it by wrapping the standard runtime with a hardware-aware one (&lt;code&gt;nvidia-container-runtime&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 1 · The NVIDIA Container Toolkit injects the host's driver libraries and device nodes into the container at launch. Your image does not bundle the kernel driver. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-3-gpu-passthrough" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three ideas make it work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 2 · Decoupling the driver from the image is what makes accelerated containers portable. Compatibility between the host driver and your image's CUDA version still matters. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-3-gpu-passthrough" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Driver decoupling.&lt;/strong&gt; The host runs the kernel driver (&lt;code&gt;nvidia.ko&lt;/code&gt;). The image never contains it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime injection.&lt;/strong&gt; At launch the toolkit injects the matching user-space driver library (&lt;code&gt;libcuda.so&lt;/code&gt;) and the device nodes (&lt;code&gt;/dev/nvidia*&lt;/code&gt;) into the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application agility.&lt;/strong&gt; The image needs only the high-level CUDA runtime libraries, so it does not depend on the host's driver build. The host driver must still be recent enough for the CUDA version inside the image.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Launching an accelerated container
&lt;/h2&gt;

&lt;p&gt;With the toolkit installed, register it with Docker and restart the daemon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That edits &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;. You can also set it by hand, and making it the default runtime is optional, since &lt;code&gt;--gpus&lt;/code&gt; selects it per container.&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="nl"&gt;"default-runtime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"nvidia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"runtimes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"nvidia"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"nvidia-container-runtime"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"runtimeArgs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run -d --name local-inference \
  --gpus all \
  --shm-size=&lt;span class="s2"&gt;"2g"&lt;/span&gt; \
  -p 8000:8000 \
  enterprise-model-server:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it: &lt;code&gt;docker run --rm --gpus all ubuntu nvidia-smi&lt;/code&gt; should print the same GPU table as the host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;--shm-size&lt;/code&gt; matters
&lt;/h3&gt;

&lt;p&gt;PyTorch passes tensors between worker processes through shared memory (&lt;code&gt;/dev/shm&lt;/code&gt;). Docker's default is only &lt;strong&gt;64 MB&lt;/strong&gt;, so parallel data loading can crash suddenly with bus errors. Raising it, for example to &lt;code&gt;2g&lt;/code&gt;, avoids that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 4 · PyTorch uses shared memory to pass tensors between worker processes. Docker's 64 MB default is too small for that, so raise it with --shm-size. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-3-gpu-passthrough" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hard limits for a 16 GB machine
&lt;/h2&gt;

&lt;p&gt;An unconstrained container is a danger to the host. A multi-step prompt can grow the KV-cache and demand several extra gigabytes of RAM at once, so enforce limits with Linux &lt;strong&gt;cgroups&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run -d --name constrained-model \
  --cpus=&lt;span class="s2"&gt;"4.0"&lt;/span&gt; \
  --memory=&lt;span class="s2"&gt;"10g"&lt;/span&gt; \
  --memory-swap=&lt;span class="s2"&gt;"10g"&lt;/span&gt; \
  -p 11434:11434 \
  ollama/ollama:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 3 · An unconstrained model container can take the whole machine down. Kernel cgroup limits keep the damage inside the container. The 10 GB cap is an example for a 16 GB host. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-3-gpu-passthrough" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--memory="10g"&lt;/code&gt; caps the container at 10 GB and leaves about 6 GB for the host OS, networking daemons and file cache.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--memory-swap="10g"&lt;/code&gt; (equal to &lt;code&gt;--memory&lt;/code&gt;) disables swap for the container. Swapping model weights to disk slows inference by orders of magnitude, so it is better to fail fast.&lt;/li&gt;
&lt;li&gt;When the container exceeds its cap, the kernel's OOM killer stops &lt;strong&gt;that container&lt;/strong&gt;, not the host. This is the default behaviour (&lt;code&gt;--oom-kill-disable=false&lt;/code&gt;), so you do not need to set it. Never use &lt;code&gt;--oom-kill-disable=true&lt;/code&gt; without a memory limit, because then a spike can hang the whole machine.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;--gpus&lt;/code&gt; only when a GPU is present. On a CPU-only laptop, the memory and CPU caps are what protect you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat these flags as part of your deployment definition, and in Kubernetes you will express the same limits as resource requests and limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;This is the exact example from the video. These are real docker run commands that read the limits from inside the container. There is no GPU on my laptop, so only the limits half of the lesson is shown.&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;# no GPU on this machine, so limits only&lt;/span&gt;
docker run --rm --cpus=2 --memory=256m --memory-swap=256m \
  --shm-size=2g alpine:3.20 sh -c &lt;span class="s1"&gt;'
  cd /sys/fs/cgroup
  for f in memory.max memory.swap.max cpu.max; do
    echo "$f: $(cat $f)"
  done
  echo shm: $(df -h /dev/shm | tail -1 | tr -s " " | cut -d" " -f2)'&lt;/span&gt;

docker run --rm --memory=64m --memory-swap=64m alpine:3.20 \
  sh -c &lt;span class="s1"&gt;'x=$(dd if=/dev/zero bs=1M count=200 | tr "\0" x)'&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"over the cap: exit code $?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash limits.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output I got while recording the video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memory.max: 268435456
memory.swap.max: 0
cpu.max: 200000 100000
shm: 2.0G
over the cap: exit code 137
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 4:&lt;/strong&gt; Optimising model weights: quantization basics with GGUF and AWQ.&lt;/p&gt;

&lt;h1&gt;
  
  
  HardwareAcceleration #Docker #GPUs #DevOps #Linux #TechInfrastructure #CUDA #SystemAdministration
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-3-gpu-passthrough" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/ai-int-day-3-gpu-passthrough&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>gpu</category>
      <category>aiintermediateseries</category>
    </item>
    <item>
      <title>Day 2: Advanced Docker for AI — Multi-Stage Builds for Heavy Python Runtimes</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sun, 08 Nov 2026 18:28:19 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-2-advanced-docker-for-ai-multi-stage-builds-for-heavy-python-runtimes-330j</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-2-advanced-docker-for-ai-multi-stage-builds-for-heavy-python-runtimes-330j</guid>
      <description>&lt;p&gt;Containerising an AI workload is a different problem from containerising a web service. The dependencies are huge, the build tools are heavy, and an unoptimised image can pass &lt;strong&gt;10 GB&lt;/strong&gt;. Moving that across a registry slows every CI/CD run, and during a Kubernetes autoscale event each new pod must pull it before it can serve a single request.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive Dockerfile
&lt;/h2&gt;

&lt;p&gt;This is the Dockerfile most teams start with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential gcc
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;torch transformers fastapi uvicorn
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, and it has three production problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 1 · A single-stage build carries compilers, caches and wheels into production. Layer sizes here are illustrative, not measured. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-2-advanced-docker" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build artifact accumulation.&lt;/strong&gt; Compilers, intermediate wheels and pip caches are baked into read-only image layers and shipped everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excessive attack surface.&lt;/strong&gt; Development and compilation tools exist in production, which gives an attacker more to work with after a break-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment latency.&lt;/strong&gt; Pulling a large image can take minutes, which causes request timeouts while new pods start.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The multi-stage fix
&lt;/h2&gt;

&lt;p&gt;Keep the build dependencies in an ephemeral &lt;strong&gt;builder&lt;/strong&gt; stage. Copy only the compiled output into a minimal &lt;strong&gt;runtime&lt;/strong&gt; stage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 2 · Dependencies are built in stage 1. Only the finished virtual environment is copied into the clean runtime stage; everything else is discarded. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-2-advanced-docker" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 1: builder (heavy, discarded) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; build-essential curl &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv /opt/venv
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/pip &lt;span class="se"&gt;\
&lt;/span&gt;    pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# ---- Stage 2: runtime (small, ships to production) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; libgomp1 &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/venv /opt/venv&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; 10001 aiuser
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; aiuser&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=aiuser:aiuser ./src /app&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build it with BuildKit enabled, which current Docker versions use by default: &lt;code&gt;docker build -t model-api:1.0 .&lt;/code&gt;. A few notes on this file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cache mount (&lt;code&gt;--mount=type=cache&lt;/code&gt;) keeps pip's download cache &lt;strong&gt;outside&lt;/strong&gt; the image, so repeat builds in CI do not re-download multi-gigabyte wheels. Because the cache is not in the layer, &lt;code&gt;--no-cache-dir&lt;/code&gt; here only avoids writing a second copy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;libgomp1&lt;/code&gt; is the OpenMP runtime that many numerical libraries need at run time. If your stack needs other shared libraries, install them in the runtime stage too, otherwise the app fails to start with a missing &lt;code&gt;.so&lt;/code&gt; error.&lt;/li&gt;
&lt;li&gt;If you need GPU support, use a CUDA-enabled base image for both stages, or install a CUDA build of PyTorch. That is the subject of Day 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Image hygiene checklist
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 3 · Five small habits that keep AI images lean and safe. Do the apt cleanup in the same RUN command, or the deleted files still live in an earlier layer. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-2-advanced-docker" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;.dockerignore&lt;/code&gt; listing &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.venv&lt;/code&gt;, &lt;code&gt;__pycache__&lt;/code&gt; and raw test data, so the build context stays small and secrets do not leak in.&lt;/li&gt;
&lt;li&gt;Run as a non-root user (&lt;code&gt;USER aiuser&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Clean package caches in the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;RUN&lt;/code&gt; command that created them. Deleting files in a later layer does not shrink the image, because the earlier layer still holds them.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 4 · Smaller images start faster. The sizes and network speed are illustrative inputs to simple arithmetic, so measure your own registry and network. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-2-advanced-docker" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Image size is not vanity. At 100 MB/s, an 8 GB image takes about 80 seconds to pull and a 2.5 GB one about 25 seconds, and during a scale-up every new node pays that cost. Lean, deterministic images turn research dependencies into a repeatable, auditable artifact that a cluster scheduler can start quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;This is the exact example from the video. Both images are really built and measured with Docker. The 100 MB random file in the builder stage stands in for compilers and wheels, so the size gap is real but exaggerated on purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;
&lt;span class="c"&gt;# ---- builder: heavy, thrown away ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/urandom &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;toolchain.bin &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"model-api v1"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; app.txt

&lt;span class="c"&gt;# ---- runtime: small, ships ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.20&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; 10001 aiuser
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; aiuser&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /build/app.txt /app/&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["cat", "/app/app.txt"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Helper file &lt;code&gt;Dockerfile.naive&lt;/code&gt;, used by the run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM alpine:3.20
WORKDIR /build
RUN dd if=/dev/urandom of=toolchain.bin bs=1M count=100
RUN echo "model-api v1" &amp;gt; app.txt
CMD ["cat", "/build/app.txt"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Helper file &lt;code&gt;compare.sh&lt;/code&gt;, used by the run:&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;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
docker build &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.naive &lt;span class="nt"&gt;-t&lt;/span&gt; d2naive &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
docker build &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; d2lean &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"naive image: &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker save d2naive | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt; MB"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"multi-stage: &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker save d2lean | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt; MB"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"app says: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; d2lean&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"runs as uid: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; d2lean &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
docker rmi d2naive d2lean &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash compare.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output I got while recording the video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;naive image: 108 MB
multi-stage: 3 MB
app says: model-api v1
runs as uid: 10001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 3:&lt;/strong&gt; Hardware acceleration in Docker: configuring GPU passthrough and resource limits for constrained hardware.&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker #Containerization #DevOps #AIEngineering #FastAPI #CloudNative #PlatformEngineering #CyberSecurity
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-2-advanced-docker" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/ai-int-day-2-advanced-docker&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>aiintermediateseries</category>
    </item>
    <item>
      <title>Day 1: The AI Infrastructure Shift — Why the Model is Only 10% of the Production System</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sat, 07 Nov 2026 18:14:38 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-1-the-ai-infrastructure-shift-why-the-model-is-only-10-of-the-production-system-31b0</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-1-the-ai-infrastructure-shift-why-the-model-is-only-10-of-the-production-system-31b0</guid>
      <description>&lt;p&gt;In enterprise AI, a common belief is that business value comes mainly from the &lt;strong&gt;choice of foundation model&lt;/strong&gt;. Teams spend weeks benchmarking a 7-billion-parameter model against a 70-billion one, debating fine-tuning curves and polishing system prompts. Then, once a model is chosen, they hit a wall trying to move from a prototype on a laptop to a real production environment.&lt;/p&gt;

&lt;p&gt;In real-world deployments the model is roughly &lt;strong&gt;10% of the total engineering effort&lt;/strong&gt;. The other &lt;strong&gt;90%&lt;/strong&gt; is the platform infrastructure needed to &lt;strong&gt;serve, scale, observe and secure&lt;/strong&gt; that intelligence. This series is about that 90%.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A model is the engine. Nobody ships an engine to customers. They ship a car: fuel system, brakes, dashboard, locks, service schedule. This series builds the car.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 1 · The model is one block in the grid. The other eight blocks are the platform that serves, scales, observes and secures it. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Walk through the grid and you see the real job. Data has to flow in (ingestion and pipelines). Traffic has to be routed safely (ingress and a service mesh). State has to survive restarts (persistent storage and vector databases). Every change must ship safely (CI/CD and GitOps), every resource must be scheduled and capped, every request must be traceable (observability), and untrusted model-generated code must be contained (sandboxes and egress control). The model is one block of nine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 2 · Teams spend weeks choosing a model, then discover that most of the work is the platform around it. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The hidden technical debt in machine learning
&lt;/h2&gt;

&lt;p&gt;In 2015, Google researchers published a now-classic paper, &lt;em&gt;Hidden Technical Debt in Machine Learning Systems&lt;/em&gt;. It showed that the machine-learning code in a real system is a small box surrounded by a much larger maze of infrastructure plumbing. With today's large language models (LLMs), that maze has grown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure plumbing:&lt;/strong&gt; API rate limits, load balancing for high-concurrency prompts, context-window exhaustion, and database connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource allocation:&lt;/strong&gt; inference is compute-bound and memory-bound. Unlike a stateless Node.js or Go service that sips memory, an LLM needs continuous GPU memory (VRAM), careful &lt;strong&gt;KV-cache&lt;/strong&gt; management and hardware acceleration passed through to the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State and synchronization:&lt;/strong&gt; real-time systems must keep vector databases, caches such as Redis, and model weights in step with low latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The memory problem, in numbers
&lt;/h3&gt;

&lt;p&gt;The KV-cache is the model's working memory for the conversation. Every token a request holds in its context adds to it, for every layer of the network. That is why a model that fits comfortably for one user can run out of memory at ten.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 4 · Weights are a fixed cost. The KV-cache grows with every long request, so a model that fits one user can run out of memory at ten. Approximate, before runtime overhead. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the arithmetic as a few lines of Python. The architecture numbers match a typical 8-billion-parameter model (32 layers, 8 key-value heads, a head size of 128). It is a rough budget that ignores runtime overhead, and I have run 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="c1"&gt;# Rough GPU memory budget for serving one 8-billion-parameter model.
&lt;/span&gt;&lt;span class="n"&gt;GIB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weights_gib&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="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes_per_param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&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;float&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;params&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bytes_per_param&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;GIB&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;kv_cache_gib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&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;layers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kv_heads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head_dim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes_per_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&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;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Keys and values (x2), for every layer, for every token held in context.
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;layers&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;kv_heads&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;head_dim&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;bytes_per_value&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;GIB&lt;/span&gt;

&lt;span class="n"&gt;gpu_gib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes_per_param&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;FP16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INT8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INT4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;weights_gib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8e9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes_per_param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;per_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;kv_cache_gib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;gpu_gib&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;per_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: weights &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GiB, one 8k-token request &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;per_request&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GiB, &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;about &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; concurrent requests fit in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gpu_gib&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; GiB&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;Notice what changes the answer: &lt;strong&gt;quantization&lt;/strong&gt; shrinks the weights, which leaves room for more concurrent requests. We cover that in Day 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fragility of unmanaged inference
&lt;/h2&gt;

&lt;p&gt;The fastest way to get a model online is to load it in a Python script and wrap it in Flask or a basic FastAPI app. It works in a demo. In production it fails in three predictable ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory leaks and out-of-memory panics:&lt;/strong&gt; concurrent long prompts exhaust memory, and the Linux kernel's OOM-killer terminates the process with no warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No fault isolation:&lt;/strong&gt; if the model runtime crashes, the whole application gateway goes with it, dropping live connections and losing in-flight work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security gaps:&lt;/strong&gt; unsandboxed execution lets a prompt-injection attack reach the host filesystem, internal network sockets or environment variables, which often hold secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 3 · A raw script takes the whole gateway down when it runs out of memory. Isolated pods contain the failure, and the platform replaces the failed one. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The platform engineering mandate
&lt;/h2&gt;

&lt;p&gt;Reliable AI systems come from platform engineering habits. Stop treating AI as an isolated data-science experiment, and treat the inference runtime as a mission-critical &lt;strong&gt;infrastructure tier&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Containerization:&lt;/strong&gt; package the runtime as a deterministic, immutable, multi-stage Docker image (Days 2 and 3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource throttling:&lt;/strong&gt; enforce memory and CPU quotas with Linux kernel &lt;strong&gt;cgroups&lt;/strong&gt;, so one runaway request cannot take down a node (Day 3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network isolation:&lt;/strong&gt; use a zero-trust service mesh and eBPF-based networking to restrict what a model or agent can reach (Week 3).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 5 · Containerize for repeatability, throttle with kernel quotas, and isolate the network, so one misbehaving model cannot hurt anything else. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  A quick readiness check
&lt;/h3&gt;

&lt;p&gt;Before you call a model "production-ready", ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What happens when the process runs out of memory, and who restarts it?&lt;/li&gt;
&lt;li&gt;How many concurrent long requests fit before it fails, and has that been measured?&lt;/li&gt;
&lt;li&gt;Can the image be rebuilt identically next month, on any machine?&lt;/li&gt;
&lt;li&gt;What can the model, or an agent running its code, reach on the network?&lt;/li&gt;
&lt;li&gt;If an answer is wrong at 3 a.m., can you trace which step caused it?&lt;/li&gt;
&lt;li&gt;How does a new version reach production, and how do you roll it back?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cannot answer most of these, the model is not the problem. The platform is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the next 30 days build
&lt;/h2&gt;

&lt;p&gt;Each week wraps another ring around the model, until you have something you could actually run in production, on a laptop with 16 GB of RAM.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; FIG 6 · Each week adds a ring around the model: containers, Kubernetes, network and security, then agents and CI/CD. &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reliable enterprise AI begins at the operating system and networking layer, not in the prompt window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 2:&lt;/strong&gt; Advanced Docker for AI: multi-stage builds for heavy Python runtimes.&lt;/p&gt;

&lt;h1&gt;
  
  
  AIInfrastructure #MLOps #PlatformEngineering #CloudNative #DevOps #EnterpriseAI #SystemDesign #TechLeadership
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/ai-int-day-1-ai-infrastructure-shift&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>infrastructure</category>
      <category>aiintermediateseries</category>
    </item>
    <item>
      <title>Day 30: The 10-Year Horizon — Architecting IT Strategy for the AI-Native Enterprise</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Fri, 30 Oct 2026 18:47:28 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-30-the-10-year-horizon-architecting-it-strategy-for-the-ai-native-enterprise-516e</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-30-the-10-year-horizon-architecting-it-strategy-for-the-ai-native-enterprise-516e</guid>
      <description>&lt;p&gt;Over the coming decade, AI is likely to move from an experimental add-on to the &lt;strong&gt;operating fabric&lt;/strong&gt; of enterprise technology. Nobody can predict the details, but the direction is clear enough to plan for. For IT leaders running shared services and infrastructure portfolios, the job shifts from managing a pile of applications to &lt;strong&gt;architecting a unified platform&lt;/strong&gt; that people and autonomous agents both use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Think of city planning, not building one house. You lay down roads, power and water so that whatever gets built on top, today or in ten years, simply plugs in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; An AI-native enterprise rests on a unified, governed data fabric, an orchestration layer that can swap models, identity and access management for autonomous agents, and human-centric leadership in which engineers direct and audit automated systems. &lt;a href="https://sureshpallapothu.in/blog/day-30-ten-year-horizon" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pillar 1: A unified data fabric
&lt;/h2&gt;

&lt;p&gt;AI is only as effective as the data it can reach. Replace fragmented application databases with a governed &lt;strong&gt;data fabric&lt;/strong&gt; that exposes business logic and data to human teams and to agents through the same controlled interfaces. Day 11 to Day 14 showed how retrieval depends on it, and Day 29 showed why you must monitor it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillar 2: Commoditised compute and a swappable model layer
&lt;/h2&gt;

&lt;p&gt;Models and hardware keep getting cheaper and more alike. The lasting advantage is rarely owning one particular model. It is having an &lt;strong&gt;orchestration layer&lt;/strong&gt; that handles routing, evaluation, cost and policy behind a stable interface, so you can swap the model underneath as price and quality change, and your applications do not notice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Models and hardware keep commoditising, so the lasting advantage is an orchestration layer that handles routing, evaluation, cost and policy behind a stable interface, so any foundation model can be swapped in as price and quality change without changing the applications. &lt;a href="https://sureshpallapothu.in/blog/day-30-ten-year-horizon" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pillar 3: Zero-trust identity for autonomous entities
&lt;/h2&gt;

&lt;p&gt;When agents execute tasks, manage infrastructure and deploy code, identity and access management has to cover &lt;strong&gt;non-human workers&lt;/strong&gt;: granular permissions, short lifetimes and a full audit trail, exactly the zero-trust ideas from Day 26.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; As agents run tasks and deploy code, identity and access management must authenticate non-human workers with granular, time-bound and auditable privileges, much like giving a contractor a visitor badge for one job. &lt;a href="https://sureshpallapothu.in/blog/day-30-ten-year-horizon" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pillar 4: Human-centric leadership
&lt;/h2&gt;

&lt;p&gt;Automation takes over repetitive execution, which lifts engineers toward designing, directing and auditing complex systems. The teams that do well will be those who can orchestrate and question autonomous systems, not just operate them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; As automation handles repetitive execution, engineers spend more time directing, auditing and architecting autonomous systems. The shares shown are illustrative and are not a forecast. &lt;a href="https://sureshpallapothu.in/blog/day-30-ten-year-horizon" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the swappable-model idea in a few lines. It assumes OpenAI-compatible endpoints, which most hosted and self-hosted engines (Ollama, vLLM and many providers) offer. The model names and prices are placeholders.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# One place that knows about models. Apps ask for a capability, not a vendor.
&lt;/span&gt;&lt;span class="n"&gt;MODELS&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;cheap-fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://ollama.internal:11434/v1&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;model&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;llama3.1:8b&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;key&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;x&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;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.provider.example/v1&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;model&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;big-model-v2&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;key&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;env:PROVIDER_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;ROUTES&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;summarise&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;cheap-fast&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;contract-review&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;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# change a line, not an app
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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;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;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MODELS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;resolve_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                                           &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="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="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where this leaves you
&lt;/h2&gt;

&lt;p&gt;Over 30 days we went from what AI is, through data, models, APIs, retrieval, infrastructure, containers and Kubernetes, to agents, security, observability, human oversight and governance. The through-line is simple: build systems that are &lt;strong&gt;deterministic where they must be, secure by default, observable, and led by people&lt;/strong&gt;. Do that today and you are laying the foundation for the AI-native enterprise of tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI Intermediate: Infrastructure and Agentic Systems&lt;/strong&gt; is the next course, starting on 7 November, after a one-week break. Thank you for following along, and share this series with someone who is starting their AI journey.&lt;/p&gt;

&lt;h1&gt;
  
  
  ITStrategy #FutureOfTech #CIO #EnterpriseArchitecture #DigitalTransformation
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-30-ten-year-horizon" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-30-ten-year-horizon&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>strategy</category>
      <category>enterprisearchitecture</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 29: Managing Technical Debt, Drift and Model Governance in Enterprise IT</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Thu, 29 Oct 2026 19:10:25 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-29-managing-technical-debt-drift-and-model-governance-in-enterprise-it-b7</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-29-managing-technical-debt-drift-and-model-governance-in-enterprise-it-b7</guid>
      <description>&lt;p&gt;Shipping a model is not the finish line. Ordinary software stays exactly as correct as it was on release day until someone changes the code. An AI system can get &lt;strong&gt;worse without anyone touching it&lt;/strong&gt;, because the world feeding it keeps moving. That slow decay is called &lt;strong&gt;drift&lt;/strong&gt;, and managing it is the heart of long-term AI maintenance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A map is accurate the day it is printed. A year later new roads exist, and the map still looks confident while sending you down a closed street.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Data drift happens when the distribution of production inputs moves away from the training data, for example when a firmware update changes the units a device reports. The model was never trained on the new range, so its accuracy degrades quietly. &lt;a href="https://sureshpallapothu.in/blog/day-29-drift-and-governance" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Two kinds of drift
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data drift (covariate shift):&lt;/strong&gt; the statistics of the production inputs move away from the training data. For example, a firmware update makes switches report a value in new units, and a model that parses that telemetry suddenly sees numbers in a range it never learned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concept drift:&lt;/strong&gt; the relationship between inputs and the correct answer changes. A fraud model trained on old spending patterns starts misclassifying transactions after procurement policy changes. The inputs look normal, but the right answer is different.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Data drift means the inputs change, such as new units or formats. Concept drift means the relationship between inputs and the correct answer changes, such as a fraud model facing a new procurement policy. Both cause silent decay without raising an error. &lt;a href="https://sureshpallapothu.in/blog/day-29-drift-and-governance" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Governance controls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated drift detection.&lt;/strong&gt; Continuously compare production data with the baseline using a statistic such as the &lt;strong&gt;Population Stability Index (PSI)&lt;/strong&gt; or Kullback-Leibler divergence, and alert when it crosses a limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model inventory and lineage.&lt;/strong&gt; Keep one registry of every active model: its training data version, quantization settings, licence obligations (for example Apache 2.0 versus a non-commercial licence) and business owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retraining and retirement.&lt;/strong&gt; Set schedules for retraining and a clear path for retiring old models, so obsolete weights and unmaintained integrations do not pile up as technical debt.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Population Stability Index compares production data with the baseline, feature by feature. A common rule of thumb treats under 0.1 as stable, 0.1 to 0.25 as worth watching and above 0.25 as needing action. Kullback-Leibler divergence is another option, and the thresholds should be tuned to your data. &lt;a href="https://sureshpallapothu.in/blog/day-29-drift-and-governance" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Governance means keeping a central registry of every active model with its training data version, quantization, licence obligations and business owner, and defining a path from registration through monitoring and retraining to retirement. The example entry is illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-29-drift-and-governance" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a PSI check in Python. It assumes &lt;code&gt;baseline&lt;/code&gt; and &lt;code&gt;current&lt;/code&gt; are NumPy arrays of one numeric feature. The 0.1 and 0.25 limits are common rules of thumb, not laws, so tune them to your data.&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;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;psi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bins&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;10&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;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quantile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bins&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# bins from the training data
&lt;/span&gt;    &lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inf&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&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;baseline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&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;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1e-6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&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="mf"&gt;1e-6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# avoid log(0)
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;psi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_latency_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_week_latency_ms&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;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PSI &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: significant drift, investigate or retrain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&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="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PSI &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: moderate drift, watch closely&lt;/span&gt;&lt;span class="sh"&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;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PSI &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: stable&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;Run a check like this per feature on a schedule, send the results to your monitoring system, and tie alerts to an owner listed in the inventory. Proactive governance catches silent failures before your users do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 30:&lt;/strong&gt; The 10-year horizon: architecting IT strategy for the AI-native enterprise.&lt;/p&gt;

&lt;h1&gt;
  
  
  ModelGovernance #TechnicalDebt #MLOps #DataDrift #EnterpriseIT
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-29-drift-and-governance" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-29-drift-and-governance&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mlops</category>
      <category>governance</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 28: Human-in-the-Loop Architecture — Machine Second, Human First in Practice</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Wed, 28 Oct 2026 19:03:07 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-28-human-in-the-loop-architecture-machine-second-human-first-in-practice-3jha</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-28-human-in-the-loop-architecture-machine-second-human-first-in-practice-3jha</guid>
      <description>&lt;p&gt;Efficiency is the goal of automation, but in high-consequence settings, full autonomy is the wrong target. Actions that change production data, move money, reconfigure network hardware or contact customers should combine the machine's speed with verified human judgment. The principle is &lt;strong&gt;Human First, Machine Second&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A junior colleague can read, research and draft all day without asking. But before they wire money or delete a database, they get a sign-off from someone senior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Define the decision boundary
&lt;/h2&gt;

&lt;p&gt;Divide actions into two classes with automatic gates.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reversible, low-impact (fully autonomous):&lt;/strong&gt; reading documentation, summarising logs, querying read-only metrics, drafting a remediation plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Irreversible or high-impact (gated):&lt;/strong&gt; dropping a table, changing a switch VLAN, deploying to production, approving spending.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Decide the class in code and policy, using an allow-list of action types. Do not ask the model whether its own action is risky, because a confused or manipulated model will answer wrongly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Low-impact reversible actions such as reading docs, summarising logs and drafting plans can run autonomously. Irreversible or high-impact actions such as dropping tables, changing network configuration, deploying to production or approving spend need human approval. The classification is set in code and policy, not by the model. &lt;a href="https://sureshpallapothu.in/blog/day-28-human-in-the-loop" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Engineer the approval gate
&lt;/h2&gt;

&lt;p&gt;A human-in-the-loop workflow needs an &lt;strong&gt;asynchronous pause and resume&lt;/strong&gt; inside your orchestration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Interrupt.&lt;/strong&gt; When an action is high-impact, the agent stops, saves its state to durable storage and enters a &lt;code&gt;WAITING_FOR_APPROVAL&lt;/code&gt; state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notify with context.&lt;/strong&gt; An alert goes to Slack, Teams or a portal with the proposed command, the agent's reasoning and the expected outcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorise.&lt;/strong&gt; The human approves or rejects, and the decision is authenticated and signed so it cannot be forged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume or revise.&lt;/strong&gt; On a verified approval the saved state is restored and the action runs. On a rejection the human's note is added to the agent's context so it drafts a safer alternative.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; When an action meets the high-impact criteria the agent halts and saves its state, a notification with the proposed command and reasoning goes to a human, the human approves or rejects with authenticated authorisation, and the workflow resumes from stored state or revises its plan. &lt;a href="https://sureshpallapothu.in/blog/day-28-human-in-the-loop" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A good approval request shows the proposed command, the agent's reasoning and the expected outcome in one place, with approve and reject actions. The example is illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-28-human-in-the-loop" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; If the human rejects a proposed action, their note is added to the agent's context so it drafts a safer alternative. This keeps people reviewing only the actions that matter while the machine does the legwork. &lt;a href="https://sureshpallapothu.in/blog/day-28-human-in-the-loop" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the core logic in Python. It assumes a database helper &lt;code&gt;db&lt;/code&gt; for saving state and a shared secret for signing approvals. In production you would use your identity provider and a proper signing scheme, and a framework such as LangGraph offers built-in interrupt and resume.&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;hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;HIGH_IMPACT&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;drop_table&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;change_vlan&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;deploy_prod&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;approve_spend&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# policy, not model opinion
&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;load-from-a-secrets-manager&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_or_pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&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;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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;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;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;HIGH_IMPACT&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;perform&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="c1"&gt;# reversible: just do it
&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;WAITING_FOR_APPROVAL&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;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;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;notify_approvers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&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="n"&gt;reasoning&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# command + why + expected outcome
&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;paused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&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;decision&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;approver&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;msg&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;approver&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&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;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&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;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&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;decision&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;approver&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;signature&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;note&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;approver&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;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid approval signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# never trust an unsigned decision
&lt;/span&gt;    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&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;decision&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve&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;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&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;saved&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&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;human_feedback&lt;/span&gt;&lt;span class="sh"&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;note&lt;/span&gt;                      &lt;span class="c1"&gt;# rejection: teach the next attempt
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;replan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&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;Balancing autonomous reasoning with human oversight keeps the organisation safe while still capturing most of the productivity gain, because people only review the small share of actions that carry real risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 29:&lt;/strong&gt; Managing technical debt, drift and model governance in enterprise IT.&lt;/p&gt;

&lt;h1&gt;
  
  
  HumanInTheLoop #HITL #EnterpriseGovernance #TechLeadership #ProcessAutomation
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-28-human-in-the-loop" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-28-human-in-the-loop&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>humanintheloop</category>
      <category>governance</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 27: Observability and Tracing for Agentic Systems — Telemetry, Logs and Metrics</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Tue, 27 Oct 2026 18:52:51 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-27-observability-and-tracing-for-agentic-systems-telemetry-logs-and-metrics-155k</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-27-observability-and-tracing-for-agentic-systems-telemetry-logs-and-metrics-155k</guid>
      <description>&lt;p&gt;Standard monitoring watches status codes, latency and memory. Those numbers can look perfect while an agent is failing. It can return &lt;strong&gt;HTTP 200 OK&lt;/strong&gt; with a completely made-up answer, or burn through forty planning steps going in circles. To debug an agent you need to see its &lt;strong&gt;decisions&lt;/strong&gt;, not only its servers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A flight tracker that only shows "plane is in the air" tells you nothing about whether it is heading to the right city. You need the flight path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; An agent can return HTTP 200 with normal latency while giving a hallucinated answer or looping on planning. Standard server health metrics cannot see that, so agent systems need traces that record their decisions. &lt;a href="https://sureshpallapothu.in/blog/day-27-agent-observability" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The three pillars of agent observability
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt; capture the end-to-end path of a run. Every model call, retrieval and tool call becomes a nested &lt;strong&gt;span&lt;/strong&gt; with its time, input tokens and output tokens, plus enough prompt context to reproduce the step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; track the cost and health of the system: token use, cache hit ratio, context-window utilisation and cost per transaction, by team or workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt; record structured state changes in a multi-agent graph, so you can find the step where the reasoning diverged from what you expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A trace records every step of an agent run as nested spans: planning, retrieval, tool calls and answer generation, each with its latency and its input and output token counts. The span names and numbers are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-27-agent-observability" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Agent observability uses three kinds of data. Traces show the path of one run, metrics track tokens, cache hits, context use and cost, and logs record state changes so you can find where reasoning diverged. &lt;a href="https://sureshpallapothu.in/blog/day-27-agent-observability" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Standards: OpenTelemetry and correlation IDs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vendor-neutral instrumentation.&lt;/strong&gt; Using &lt;strong&gt;OpenTelemetry (OTel)&lt;/strong&gt; means your telemetry can flow into Prometheus, Grafana, Jaeger or a commercial backend without locking you to one vendor. OpenTelemetry also has emerging conventions for generative AI attributes, which are still evolving, so check the current names.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlation IDs.&lt;/strong&gt; Give each incoming request a unique trace ID that follows it through the gateway, application, vector database and agent workers. Search that ID and you see one user interaction end to end.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A unique trace or correlation ID attached to each incoming request travels through the gateway, application, vector database and agent workers, so operators can follow one user interaction end to end. OpenTelemetry propagates it across services. &lt;a href="https://sureshpallapothu.in/blog/day-27-agent-observability" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a minimal Python example. It assumes you have installed &lt;code&gt;opentelemetry-sdk&lt;/code&gt; and &lt;code&gt;opentelemetry-exporter-otlp&lt;/code&gt;, and that a collector or a recent Jaeger is listening for OTLP on port 4317. The &lt;code&gt;llm&lt;/code&gt; and &lt;code&gt;run_tool&lt;/code&gt; functions stand in for your own.&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;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.exporter.otlp.proto.grpc.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;

&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost:4317&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insecure&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;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&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;request_id&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent.run&lt;/span&gt;&lt;span class="sh"&gt;"&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;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm.plan&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                  &lt;span class="c1"&gt;# your model call
&lt;/span&gt;            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.input_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gen_ai.usage.output_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&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.run_sql&lt;/span&gt;&lt;span class="sh"&gt;"&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;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&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.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_sql&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_sql&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&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.rows_returned&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;rows&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# 0 rows is a useful signal
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caution: prompts can contain customer or internal data, so redact sensitive fields before they go into spans or logs. With tracing in place, agent debugging moves from guesswork to something you can inspect, replay and audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 28:&lt;/strong&gt; Human-in-the-loop architecture: machine second, human first in practice.&lt;/p&gt;

&lt;h1&gt;
  
  
  Observability #OpenTelemetry #DistributedTracing #MLOps #SRE
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-27-agent-observability" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-27-agent-observability&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 26: Zero-Trust Security for AI — Sandboxing Ephemeral Execution and Model Egress</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Mon, 26 Oct 2026 19:31:01 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-26-zero-trust-security-for-ai-sandboxing-ephemeral-execution-and-model-egress-ilc</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-26-zero-trust-security-for-ai-sandboxing-ephemeral-execution-and-model-egress-ilc</guid>
      <description>&lt;p&gt;Once an agent can &lt;strong&gt;run code&lt;/strong&gt; (a Python script to analyse a dataset, a command on a server), the old network perimeter stops protecting you. The code was written by a model, and you cannot treat it as trusted. A zero-trust design starts from one assumption: &lt;strong&gt;the model will eventually produce something buggy, unsafe or compromised&lt;/strong&gt;, so the system must stay safe anyway.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Letting an agent run code on your server is like letting a stranger use your office computer. You would give them a locked guest room with no network cable, a time limit, and a visitor badge that opens nothing important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Under zero-trust principles you assume the model will eventually produce buggy, unsafe or compromised code. Generated code must never run on a host or production container. It runs inside a sandbox that limits what it can steal, escalate or change. &lt;a href="https://sureshpallapothu.in/blog/day-26-zero-trust-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Runtime sandboxing: shrink the blast radius
&lt;/h2&gt;

&lt;p&gt;Running generated code directly on a host or in a production container risks data theft, privilege escalation and damaging changes. Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation.&lt;/strong&gt; Run it in an unprivileged, ephemeral environment such as a &lt;strong&gt;gVisor&lt;/strong&gt; sandbox, a &lt;strong&gt;Firecracker microVM&lt;/strong&gt; or an unprivileged throwaway container. MicroVMs and gVisor give a stronger boundary than a plain container, because they do not share the host kernel directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immutable filesystem.&lt;/strong&gt; Give it read-only access to the base system and a temporary in-memory disk that is destroyed as soon as the task ends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource throttling.&lt;/strong&gt; Use Linux &lt;strong&gt;cgroups&lt;/strong&gt; limits on CPU, memory, disk I/O and process count, so a runaway or malicious script cannot starve the machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Agent-generated code runs inside layered isolation: a microVM or gVisor boundary, a read-only filesystem with a temporary in-memory disk, and kernel cgroup limits on CPU, memory and I/O. The whole environment is destroyed as soon as the task ends. &lt;a href="https://sureshpallapothu.in/blog/day-26-zero-trust-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Egress and privilege minimisation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No network by default.&lt;/strong&gt; A sandbox should start with no network access at all. If a task needs one external API, allow that single destination and keep everything else blocked, so nothing can be sent out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege.&lt;/strong&gt; Database credentials handed to an agent should be read-only, limited to non-sensitive schemas and short-lived. Administrative actions, such as schema changes, need an authenticated elevation step.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A code-execution sandbox should have no network access by default. If a task needs an external API, allow that single destination and keep everything else, including the local network and the internet, blocked so data cannot leak out. &lt;a href="https://sureshpallapothu.in/blog/day-26-zero-trust-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Credentials given to an agent should be read-only, limited to non-sensitive schemas and short-lived. Administrative actions such as schema changes require an authenticated elevation step, and everything is logged. &lt;a href="https://sureshpallapothu.in/blog/day-26-zero-trust-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a practical starting point: a Python helper that runs untrusted code in a locked-down throwaway container. It assumes Docker is installed. For stronger isolation, add &lt;code&gt;--runtime=runsc&lt;/code&gt; if you have gVisor set up, or use a microVM platform.&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;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_untrusted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&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;timeout_s&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;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TemporaryDirectory&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;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cmd&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;docker&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;run&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;--rm&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;--network&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;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# zero network access
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--read-only&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                       &lt;span class="c1"&gt;# read-only root filesystem
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--tmpfs&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;/tmp:size=64m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# small in-memory scratch space
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--memory&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;256m&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;--cpus&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;0.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# cgroup limits
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--pids-limit&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;64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;# no fork bombs
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--cap-drop&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;ALL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;# drop Linux capabilities
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--security-opt&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;no-new-privileges&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;65534:65534&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# unprivileged user
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:/task.py:ro&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;python:3.11-slim&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;python&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;/task.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;try&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="n"&gt;subprocess&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="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&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;span class="n"&gt;text&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;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeoutExpired&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;Stopped: time limit exceeded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="nf"&gt;return &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;stdout&lt;/span&gt; &lt;span class="o"&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;stderr&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;# cap the output size too
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat the agent's execution environment as an &lt;strong&gt;untrusted runtime boundary&lt;/strong&gt;. That is what lets you give agents real capabilities without handing them the keys to the enterprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 27:&lt;/strong&gt; Observability and tracing for agentic systems: telemetry, logs and metrics.&lt;/p&gt;

&lt;h1&gt;
  
  
  ZeroTrust #CyberSecurity #DevSecOps #Sandboxing #AIAgents
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-26-zero-trust-ai" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-26-zero-trust-ai&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>zerotrust</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 25: Automated CI/CD — Automating the Software Lifecycle for AI Models</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sun, 25 Oct 2026 17:12:01 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-25-automated-cicd-automating-the-software-lifecycle-for-ai-models-24k5</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-25-automated-cicd-automating-the-software-lifecycle-for-ai-models-24k5</guid>
      <description>&lt;p&gt;A normal unit test checks that a function returns an exact value. AI is &lt;strong&gt;non-deterministic&lt;/strong&gt;: editing a system prompt, changing the temperature or pointing at a newer model can quietly reduce accuracy, add hallucinations or break the JSON your code depends on, and no exact-match test will notice. MLOps closes the gap by putting &lt;strong&gt;evaluation&lt;/strong&gt; into the Git workflow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A restaurant does not taste-test every dish by checking its weight. It has a tasting panel with a score sheet, and a new recipe only goes on the menu if the score does not drop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The evaluation pipeline
&lt;/h2&gt;

&lt;p&gt;Any pull request that touches prompt templates, RAG indexing or agent tools should trigger automatic checks before it can merge.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema and lint tests.&lt;/strong&gt; Assert that outputs follow the declared JSON schema and regex rules across a set of test inputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gold-standard suite.&lt;/strong&gt; Run the pipeline against a version-controlled set of real edge cases and compare the pass rate with the last release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-as-judge.&lt;/strong&gt; Use a separate evaluator model to grade faithfulness, relevance and use of context, and flag a drop below the baseline.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Any pull request that changes prompt templates, RAG indexing or agent tooling must pass automated checks before merging: schema and regex tests, a gold-standard test suite, and an LLM judge scoring faithfulness and relevance. &lt;a href="https://sureshpallapothu.in/blog/day-25-ai-cicd" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Running a version-controlled gold-standard test suite on every change gives a pass-rate trend. A prompt change that drops the score below the agreed baseline is flagged before it ships. The numbers are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-25-ai-cicd" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. GitOps and automated rollout
&lt;/h2&gt;

&lt;p&gt;Release through &lt;strong&gt;GitOps&lt;/strong&gt;, with a tool such as &lt;strong&gt;ArgoCD&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Declarative infrastructure.&lt;/strong&gt; Model settings, image tags and replica counts live as manifests in a Git repository. ArgoCD keeps the cluster in line with that repository, so a rollback is a revert of one commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canary rollouts.&lt;/strong&gt; A new model version first receives a small share of real traffic, for example 5%, while error rate and latency are watched. If the numbers hold, the share grows step by step to 100%, and if a threshold is crossed the rollout reverts on its own. In the Argo ecosystem the canary itself is run by &lt;strong&gt;Argo Rollouts&lt;/strong&gt;, which works alongside ArgoCD.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; In GitOps the model version, image tag and replica limits are declared in a Git repository, and ArgoCD keeps the cluster in sync with it, pulling drifted resources back. A rollback is a revert of one commit. &lt;a href="https://sureshpallapothu.in/blog/day-25-ai-cicd" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A new model version receives a small share of traffic first, for example 5%, while error rate and latency are watched. If metrics stay healthy the share grows step by step to 100%, and if a threshold is exceeded the rollout reverts automatically. ArgoCD syncs the manifests, and Argo Rollouts performs the canary. &lt;a href="https://sureshpallapothu.in/blog/day-25-ai-cicd" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below are the two main pieces. They assume a &lt;code&gt;tests/evals/&lt;/code&gt; folder with your gold cases, a helper called &lt;code&gt;run_pipeline&lt;/code&gt;, and a judge model you provide. Treat the thresholds as starting points to tune.&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;# tests/evals/test_quality.py  (run by CI with: pytest tests/evals)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run_pipeline&lt;/span&gt;          &lt;span class="c1"&gt;# your RAG or agent entry point
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;judge&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faithfulness_score&lt;/span&gt;       &lt;span class="c1"&gt;# calls an isolated judge model
&lt;/span&gt;
&lt;span class="n"&gt;BASELINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.88&lt;/span&gt;                            &lt;span class="c1"&gt;# pass rate of the currently deployed version
&lt;/span&gt;&lt;span class="n"&gt;CASES&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;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/evals/gold_cases.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_outputs_match_schema&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CASES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sources&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;           &lt;span class="c1"&gt;# schema check
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_quality_not_below_baseline&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;faithfulness_score&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="nf"&gt;run_pipeline&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&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;CASES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;/&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;CASES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;BASELINE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;          &lt;span class="c1"&gt;# small tolerance for judge noise
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Argo Rollouts: a canary that shifts traffic in steps&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rollout&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&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;model-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;model-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;canary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;setWeight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10m&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;      &lt;span class="c1"&gt;# watch error rate and latency here&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;setWeight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10m&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;setWeight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;10m&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;setWeight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;model-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&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;model-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.example.com/model-api:1.5.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For automatic rollback you attach an analysis that queries your metrics and fails the rollout when they cross a threshold. Automating testing and release turns AI deployments from manual experiments into repeatable, verifiable engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 26:&lt;/strong&gt; Zero-trust security for AI: sandboxing ephemeral execution and model egress.&lt;/p&gt;

&lt;h1&gt;
  
  
  MLOps #CICD #GitOps #ArgoCD #PlatformEngineering
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-25-ai-cicd" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-25-ai-cicd&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mlops</category>
      <category>cicd</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 24: Multi-Agent Orchestration — Supervisor, Worker and Evaluator Patterns</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:53:08 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-24-multi-agent-orchestration-supervisor-worker-and-evaluator-patterns-3jjd</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-24-multi-agent-orchestration-supervisor-worker-and-evaluator-patterns-3jjd</guid>
      <description>&lt;p&gt;As a workflow grows, handing one agent every tool and a long list of responsibilities makes it worse. The prompt gets crowded, the model picks the wrong tool and the context window fills up. The fix is the one human organisations use: divide the work among &lt;strong&gt;specialists&lt;/strong&gt; who collaborate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A hospital does not give one doctor every job. A coordinator sends each patient to the right specialist, and a second doctor reviews the plan before treatment begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Giving one agent every tool and a huge prompt dilutes its focus and causes tool-selection mistakes. Splitting work across specialised agents with narrow scopes and short prompts is usually more reliable and easier to test. &lt;a href="https://sureshpallapothu.in/blog/day-24-multi-agent-orchestration" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Three patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Worker (specialisation).&lt;/strong&gt; A small agent with a narrow scope and its own limited tools. A log-parser worker can only read logs. A database worker has a read-only SQL connection. A tight scope improves accuracy and limits damage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supervisor (routing and delegation).&lt;/strong&gt; The coordinator. It reads a high-level request, splits it into sub-tasks, hands them to the right workers and combines the results into one answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluator (validation and guardrails).&lt;/strong&gt; An independent agent that reviews worker output against policy, accuracy rules and business logic before anything is committed or shown to the user. Keeping it separate matters, because a worker checking its own work tends to approve it.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A supervisor splits a request into sub-tasks and routes them to narrow worker agents, each with its own limited tools. An independent evaluator reviews their output against policy and quality rules before anything is committed or returned to the user. &lt;a href="https://sureshpallapothu.in/blog/day-24-multi-agent-orchestration" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  State and orchestration
&lt;/h2&gt;

&lt;p&gt;Coordinating several agents calls for a real state machine. Frameworks such as &lt;strong&gt;LangGraph&lt;/strong&gt; (Python) and &lt;strong&gt;Mastra&lt;/strong&gt; (TypeScript) model the system as a directed graph.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes&lt;/strong&gt; are agents, tool executions or human-approval checkpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges&lt;/strong&gt; are routing rules based on what earlier nodes returned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable state&lt;/strong&gt; records progress after each step (a checkpoint), so if a pod crashes mid-run the workflow resumes from the last confirmed node instead of starting over.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Frameworks such as LangGraph or Mastra model multi-agent workflows as directed graphs. Nodes are agents, tool calls or human approval checkpoints, and edges are routing decisions based on the outputs of earlier nodes. &lt;a href="https://sureshpallapothu.in/blog/day-24-multi-agent-orchestration" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Saving a checkpoint after each step means that if a pod crashes mid-run the workflow resumes from the last confirmed step instead of restarting from the beginning. Frameworks such as LangGraph provide checkpointing for this. &lt;a href="https://sureshpallapothu.in/blog/day-24-multi-agent-orchestration" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the pattern without any framework, so the idea is clear. The &lt;code&gt;ask_*&lt;/code&gt; functions stand in for model calls with different system prompts and tools, and a framework adds the graph, retries and persistent checkpoints for you.&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;run_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="c1"&gt;# Supervisor splits the request into sub-tasks (cached in state so a restart skips done work)
&lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ask_supervisor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;            &lt;span class="c1"&gt;# e.g. ["parse_logs", "check_db"]
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plan&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="n"&gt;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;                                              &lt;span class="c1"&gt;# resume: this step already finished
&lt;/span&gt;        &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WORKERS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                                    &lt;span class="c1"&gt;# each worker has its own narrow tools
&lt;/span&gt;        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;save_checkpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                    &lt;span class="c1"&gt;# durable state after every step
&lt;/span&gt;
    &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask_supervisor_combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask_evaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&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="c1"&gt;# independent quality gate
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Held for human review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;                            &lt;span class="c1"&gt;# an approval node in a real graph
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structuring agents as modular teams mirrors high-performing engineering organisations: clear roles, small scopes and a reviewer, which is what makes complex tasks dependable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 25:&lt;/strong&gt; Automated CI/CD: automating the software lifecycle for AI models.&lt;/p&gt;

&lt;h1&gt;
  
  
  MultiAgentSystems #LangGraph #SystemDesign #AgenticAI #SoftwareEngineering
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-24-multi-agent-orchestration" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-24-multi-agent-orchestration&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>multiagent</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 23: Tool Use and Function Calling — Connecting LLMs to APIs, Databases and Shells</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Fri, 23 Oct 2026 17:57:27 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-23-tool-use-and-function-calling-connecting-llms-to-apis-databases-and-shells-2iia</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-23-tool-use-and-function-calling-connecting-llms-to-apis-databases-and-shells-2iia</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iML_bkGC-qU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;By default a language model is an isolated reasoning engine. It cannot read the current time, query your private database or run a command. &lt;strong&gt;Function calling&lt;/strong&gt;, also called tool use, is the bridge that connects that engine to real systems, so the model becomes a router that decides &lt;em&gt;what&lt;/em&gt; to call while your software does the calling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; The model is a manager who can write instructions but cannot touch anything. It hands a clear, structured request to an assistant (your application), who does the task and reports back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; On its own an LLM only has its trained weights. It cannot read the current time, query a private database, call a live API or run commands. Function calling is the bridge: the model asks for an action and your software performs it. &lt;a href="https://sureshpallapothu.in/blog/day-23-function-calling" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The four-step protocol
&lt;/h2&gt;

&lt;p&gt;A common misconception is that the LLM runs the code. It does not. Function calling is a deterministic, four-step exchange.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Declare the tools.&lt;/strong&gt; Your application sends the model a list of tools described with JSON schemas: the name, what it is for and its typed parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model invocation.&lt;/strong&gt; If the question needs outside data or an action, the model replies with a structured tool call (the function name and exact arguments) instead of ordinary text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side execution.&lt;/strong&gt; Your application intercepts that reply, validates it and runs the real code: a SQL query, a REST call, a script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observation injection.&lt;/strong&gt; Your application sends the result back as a tool message, and the model uses it to finish its answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; In function calling the application declares tools as JSON schemas, the model replies with a structured tool call instead of text, the application validates and executes the real code, and the result is returned to the model as an observation so it can write the final answer. The model never runs code itself. &lt;a href="https://sureshpallapothu.in/blog/day-23-function-calling" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; The application describes a tool with a name, a purpose and typed parameters. The model answers with a structured JSON tool call naming the function and its arguments, and the application sends the real result back as an observation. The names and values are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-23-function-calling" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Type safety and validation
&lt;/h2&gt;

&lt;p&gt;Free text breaks software pipelines. Use structured arguments and validate them with JSON Schema or a library such as &lt;strong&gt;Pydantic&lt;/strong&gt;, so a malformed value is rejected before it reaches your backend. Then add checks the model cannot override: an allow-list of tools and a permission check for the current user.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Before running a tool call, the application checks the arguments against a schema, checks the tool is on an allow-list, checks the user has permission, and only then executes and logs it. Bad or unsafe calls are rejected. Pydantic or JSON Schema keeps arguments type-safe. &lt;a href="https://sureshpallapothu.in/blog/day-23-function-calling" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the loop with an OpenAI-compatible API. Ollama and vLLM from Day 16 expose the same interface, so the pattern works with a local model too. It assumes a model that supports tool calling, and &lt;code&gt;get_gpu_status&lt;/code&gt; is a stand-in for your own function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                     &lt;span class="c1"&gt;# or OpenAI(base_url="http://localhost:11434/v1", api_key="x")
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GpuArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;             &lt;span class="c1"&gt;# type-safe arguments
&lt;/span&gt;    &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_gpu_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&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;dict&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;degraded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# replace with a real lookup
&lt;/span&gt;
&lt;span class="n"&gt;TOOLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&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;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_gpu_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Returns GPU health for a node&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;parameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GpuArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_json_schema&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}}]&lt;/span&gt;
&lt;span class="n"&gt;ALLOWED&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;get_gpu_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GpuArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_gpu_status&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Is gpu-07 healthy?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ALLOWED&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&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="c1"&gt;# allow-list
&lt;/span&gt;        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_validate_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# validate
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;                               &lt;span class="c1"&gt;# execute
&lt;/span&gt;        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&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;tool_call_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;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;content&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="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;result&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;               &lt;span class="c1"&gt;# observation
&lt;/span&gt;    &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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;final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restricting the model to &lt;em&gt;proposing&lt;/em&gt; structured arguments keeps execution under your control: authenticated, validated and logged inside your own application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 24:&lt;/strong&gt; Multi-agent orchestration: supervisor, worker and evaluator patterns.&lt;/p&gt;

&lt;h1&gt;
  
  
  FunctionCalling #ToolUse #APIs #Pydantic #SoftwareArchitecture
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-23-function-calling" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-23-function-calling&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>functioncalling</category>
      <category>30dayaiseries</category>
    </item>
  </channel>
</rss>
