<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Siddharth Sambharia</title>
  <subtitle>Writing about technology, AI, and ideas. Working at Portkey.</subtitle>
  <link href="https://sambharia.com/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://sambharia.com/" rel="alternate" type="text/html" />
  <id>https://sambharia.com/feed.xml</id>
  
  
  <updated>2026-07-12T00:00:00+00:00</updated>
  
  <author>
    <name>Siddharth Sambharia</name>
    <uri>https://sambharia.com/</uri>
  </author>
  
  <entry>
    <title>How LLMs Work, From Scratch</title>
    <link href="https://sambharia.com/how-llms-work" rel="alternate" type="text/html" title="How LLMs Work, From Scratch" />
    <id>https://sambharia.com/how-llms-work</id>
    <published>2026-07-12T00:00:00+00:00</published>
    <updated>2026-07-12T00:00:00+00:00</updated>
    <summary type="html"></summary>
    <content type="html">&lt;div class=&quot;how-llms-work-post&quot;&gt;

  &lt;p&gt;&lt;img class=&quot;post-header&quot; src=&quot;/assets/how-llms-work/image.png&quot; alt=&quot;Classical painting of philosophers gathered in debate around a table&quot;&gt;&lt;/p&gt;

  &lt;p&gt;When ChatGPT launched in late 2022, it opened a whole new set of possibilities for what computers can do. Fast forward to today, AI has already started contributing to the world in many ways, from writing code to speeding up scientific progress. Over the past few years AI models have kept getting better, and so has everything you can do with them.&lt;/p&gt;

  &lt;p&gt;And yet, most of us still don&apos;t know how this technology actually works.&lt;/p&gt;

  &lt;p&gt;A Large language model, is simply just a mathematical function: given some input text, it predicts the probability of what word comes next. &lt;a href=&quot;https://sambharia.com/learn-ai&quot; target=&quot;_blank&quot;&gt;I wanted to understand how LLMs work from scratch&lt;/a&gt;, not to become an expert, but to understand the basics of this technology.&lt;/p&gt;

  &lt;p&gt;In this post, I&apos;ve broken down every step of how LLMs work from scratch, stripping away the complexity. By the end, you should have a simple mental model for how LLMs work fundamentally.&lt;/p&gt;

  &lt;div class=&quot;toc&quot;&gt;
&lt;p&gt;&lt;strong&gt;The roadmap:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
&lt;strong&gt;Tokenization&lt;/strong&gt;: text becomes integers&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Embedding&lt;/strong&gt;: integers become vectors with meaning and position&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Attention&lt;/strong&gt;: tokens read each other&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;MLP&lt;/strong&gt;: each token thinks, and facts get stored&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;LayerNorm and residuals&lt;/strong&gt;: plumbing that keeps it stable&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;The full forward pass&lt;/strong&gt;: every piece wired together, and the vector becomes the next token&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Inference&lt;/strong&gt;: the model uses that next-token prediction to generate text, one token at a time&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;

  &lt;h2 id=&quot;tokenization&quot;&gt;1. Tokenization&lt;/h2&gt;

  &lt;p&gt;LLMs are mathematical functions; &lt;strong&gt;they don&apos;t understand words&lt;/strong&gt;. We need a way to convert words to integers using a process called Tokenization.&lt;/p&gt;

  &lt;ol&gt;
  &lt;li&gt;Tokenization breaks down text into smaller units called &lt;strong&gt;tokens&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Each token is then mapped to a numerical value (Token-ID) that the model can process.&lt;/li&gt;
&lt;/ol&gt;

  &lt;p&gt;For instance: take a small dataset of 500 US names. You can map all characters in the alphabet to an integer:&lt;/p&gt;

  &lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
&lt;th&gt;Token ID&lt;/th&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;z&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;&lt;em&gt;special token&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

  &lt;p&gt;Now any name is a list of integers. &lt;code&gt;SID → [19, 8, 3]&lt;/code&gt;, &lt;code&gt;JOHN → [10, 15, 8, 14]&lt;/code&gt;.&lt;/p&gt;

  &lt;p&gt;This was a simple example, but in today&apos;s LLMs, instead of mapping each character to a token ID, models use subword tokenization techniques. Why?&lt;/p&gt;

  &lt;ol&gt;
  &lt;li&gt;Human language is fairly complex. Splitting tokens into subwords lets the model handle words it never saw in training by assembling them from pieces.&lt;/li&gt;
  &lt;li&gt;Models don&apos;t use character-to-token mapping because it makes sequences long and slow to compute.&lt;/li&gt;
&lt;/ol&gt;

  &lt;p&gt;Currently, Frontier Labs uses &lt;strong&gt;&lt;em&gt;subword&lt;/em&gt;&lt;/strong&gt; tokenization techniques like &lt;a href=&quot;https://www.perplexity.ai/search/what-is-byte-pair-encoding-bpe-MV_w_T17RWG6sZUqRl2pIA&quot; target=&quot;_blank&quot;&gt;Byte Pair Encoding (BPE)&lt;/a&gt;. BPE starts with individual characters and iteratively merges the most frequent pairs of symbols to form new tokens.&lt;/p&gt;

  &lt;figure&gt;
  &lt;img src=&quot;/assets/how-llms-work/tokenization.svg&quot; alt=&quot;The word symbiosis split into subword tokens sym, bio, sis and their token IDs&quot; style=&quot;max-width: 70%;&quot;&gt;
  &lt;figcaption style=&quot;max-width: 70%; margin-left: auto; margin-right: auto;&quot;&gt;Subword tokenization: “symbiosis” splits into tokens, each mapped to a token ID&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;Alongside the regular tokens in the dataset, models add tokens for structure.&lt;/p&gt;

  &lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;BOS&lt;/strong&gt; (beginning of sequence) sits at the start, so the first real token has something before it to look at.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;EOS&lt;/strong&gt; (end of sequence) sits at the end. During inference, producing an EOS is the signal to stop. Without it, the model runs forever.&lt;/li&gt;
&lt;/ul&gt;

  &lt;p&gt;Some models use one token for both jobs. For example, a name will be represented as &lt;code&gt;[BOS, S, I, D, EOS]&lt;/code&gt;&lt;/p&gt;

  &lt;p&gt;The model now has a predefined vocabulary of words with an integer value attached to each entry.&lt;/p&gt;

  &lt;h2 id=&quot;embedding-giving-tokens-meaning&quot;&gt;2. Embedding: giving tokens meaning&lt;/h2&gt;

  &lt;p&gt;Token IDs are &lt;strong&gt;arbitrary integers&lt;/strong&gt;; they don&apos;t have any meaning.&lt;/p&gt;

  &lt;p&gt;&lt;code&gt;a = 0, b = 1&lt;/code&gt;…&lt;/p&gt;

  &lt;p&gt;The numbers mean nothing by themselves. We need a way to give meaning to these tokens.&lt;/p&gt;

  &lt;p&gt;Embeddings fix this.&lt;/p&gt;

  &lt;blockquote&gt;Picture a library where each book (words) has a barcode (token-ID) stuck on it. The barcode says nothing about the book. What you actually want is a description: topic: food, difficulty: easy, language: english. An embedding is that description, written as a vector of numbers.&lt;/blockquote&gt;

  &lt;p&gt;Embedding is a &lt;strong&gt;learned vector of numbers that stores information about the token&lt;/strong&gt;, for the model to do math on it.&lt;/p&gt;

  &lt;h3&gt;Word-Token-Embedding&lt;/h3&gt;

  &lt;p&gt;Every model keeps an &lt;strong&gt;embedding matrix&lt;/strong&gt;. Every token ID has a vector associated with it. For instance, take a small vocabulary with a 3-dimensional embedding vector; an embedding matrix for it looks like this (showing just a few tokens):&lt;/p&gt;

  &lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
&lt;th&gt;Token ID&lt;/th&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Embedding vector&lt;/th&gt;
&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&quot;cat&quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.9, 0.1, 0.8]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&quot;kitten&quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.85, 0.15, 0.75]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&quot;car&quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.1, 0.9, 0.2]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

  &lt;figure&gt;
  &lt;img src=&quot;/assets/how-llms-work/embedding-space.svg&quot; alt=&quot;Scatter plot of the embedding vectors: cat, dog and kitten cluster as animals; car and truck cluster as vehicles&quot; style=&quot;max-width: 70%;&quot;&gt;
  &lt;figcaption style=&quot;max-width: 70%; margin-left: auto; margin-right: auto;&quot;&gt;Tokens with similar meaning land close together in embedding space&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;For instance, in this example you can see:&lt;/p&gt;

  &lt;p&gt;“cat” &lt;code&gt;[0.9, 0.1, 0.8]&lt;/code&gt; and “kitten” &lt;code&gt;[0.85, 0.15, 0.75]&lt;/code&gt; are very close in the 3-D space. While “car” &lt;code&gt;[0.1, 0.9, 0.2]&lt;/code&gt; is far away, even though its ID (2) is right next to cat&apos;s (0).&lt;/p&gt;

  &lt;p&gt;The embedding vector encodes &lt;strong&gt;meaning&lt;/strong&gt; about the token that is learned in the training process. Maybe the first dimension captures “animal-ness,” the second “vehicle-ness.” Nobody set them by hand. The model learned them.&lt;/p&gt;

  &lt;p&gt;Because the vectors hold meaning, something surprising happens. After training, you can do meaningful arithmetic on tokens:&lt;/p&gt;

  &lt;blockquote&gt;&lt;code&gt;vec(&quot;king&quot;) - vec(&quot;man&quot;) + vec(&quot;woman&quot;) &amp;amp;approx; vec(&quot;queen&quot;)&lt;/code&gt;&lt;/blockquote&gt;

  &lt;p&gt;Nobody coded that. It fell out of training.&lt;/p&gt;

  &lt;p&gt;This embedding matrix is called &lt;strong&gt;WTE&lt;/strong&gt; (word token embedding) matrix. The dimension of the WTE matrix is (no. of token IDs, dimension of embedding), which is &lt;code&gt;(50257, 768)&lt;/code&gt; for GPT-2.&lt;/p&gt;

  &lt;h3&gt;Word-Position-Embedding&lt;/h3&gt;

  &lt;p&gt;Let&apos;s take two sentences:&lt;/p&gt;

  &lt;blockquote&gt;“The dog bit the man”&lt;br&gt;“The man bit the dog”&lt;/blockquote&gt;

  &lt;p&gt;Both snippets have the same tokens, but &lt;strong&gt;the order of tokens changes the meaning completely&lt;/strong&gt;.&lt;/p&gt;

  &lt;p&gt;The embedding vector for “man” in both cases is the same. We need a way to add the positional meaning to each vector.&lt;/p&gt;

  &lt;p&gt;The fix is a second matrix, &lt;strong&gt;WPE&lt;/strong&gt; (word position embedding).&lt;/p&gt;

  &lt;p&gt;For instance, take a model with a context window (the maximum tokens an AI model can process in a single request) of 5 tokens, with a 3-dimensional position embedding vector; the WPE looks like this:&lt;/p&gt;

  &lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
&lt;th&gt;Position&lt;/th&gt;
&lt;th&gt;Position vector&lt;/th&gt;
&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.00, 0.10, 0.00]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.30, 0.20, 0.40]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.10, 0.40, 0.60]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.85, 0.12, 0.50]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[0.15, 0.45, 0.75]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

  &lt;p&gt;The dimension of the embedding vector is (context window × dimension of embedding vector). GPT-2&apos;s WPE is &lt;code&gt;(1024, 768)&lt;/code&gt;.&lt;/p&gt;

  &lt;p&gt;The input to the next transformer block is just the two added together:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;X&lt;sub&gt;i&lt;/sub&gt; = WTE[token]  (what it is)
   + WPE[i]      (where it sits)&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;For “dog” sitting in position 1, the final embedding vector that gets passed to the next stage is:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;X(dog) = [0.8, 0.2, 0.7]   (its WTE embedding)
       + [0.0, 0.1, 0.0]   (the position-1 vector from WPE)&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Now “dog” in position 2 and “dog” in position 5 look different, and word order finally means something.&lt;/p&gt;

  &lt;h2 id=&quot;attention&quot;&gt;3. Attention&lt;/h2&gt;

  &lt;p&gt;Now we have an input X per token that encodes its meaning and position.&lt;/p&gt;

  &lt;p&gt;Consider this example:&lt;/p&gt;

  &lt;blockquote&gt;“She walked to the river &lt;strong&gt;bank&lt;/strong&gt; to fish.”&lt;br&gt;“She called her investment &lt;strong&gt;bank&lt;/strong&gt; about the loan.”&lt;/blockquote&gt;

  &lt;p&gt;The token ‘bank’ has the exact same embedding vector, but &lt;strong&gt;it means completely different things in both examples&lt;/strong&gt;. A fixed vector can&apos;t be both a riverbank and a financial one.&lt;/p&gt;

  &lt;p&gt;The model needs a way to update its vector to encode information from its surroundings. If the token ‘bank’ is near “river” and “fish,” it should lean geographic.&lt;/p&gt;

  &lt;p&gt;It&apos;s the job of the Attention Block in the Transformer where &lt;strong&gt;the tokens communicate with each other to update their values based on context&lt;/strong&gt;. Figure out which earlier tokens are relevant and what to add to X(bank) to produce a refined X′(bank) that encodes the meaning of the words that came before it.&lt;/p&gt;

  &lt;p&gt;Attention is the trickiest concept to wrap your head around in this blog. Let&apos;s take the phrase “&lt;em&gt;her investment bank&lt;/em&gt;”. Here&apos;s how Attention works in 3 parts:&lt;/p&gt;

  &lt;ol&gt;
  &lt;li&gt;
&lt;strong&gt;Query (Q)&lt;/strong&gt;: “bank” asks, who can help me predict the next token.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Key (K)&lt;/strong&gt;: every other token before ‘bank’ in the model&apos;s context presents its offer.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Value (V)&lt;/strong&gt;: the actual information the other tokens in context hand over once picked.&lt;/li&gt;
&lt;/ol&gt;

  &lt;p&gt;Now the mechanics.&lt;/p&gt;

  &lt;p&gt;Attention introduces three learned matrices, &lt;strong&gt;W&lt;sub&gt;q&lt;/sub&gt;, W&lt;sub&gt;k&lt;/sub&gt;, W&lt;sub&gt;v&lt;/sub&gt;&lt;/strong&gt;, and each token multiplies its &lt;code&gt;X&lt;/code&gt; by them to produce three vectors.&lt;/p&gt;

  &lt;p&gt;A quick note before we dive in: an &lt;strong&gt;attention head&lt;/strong&gt; is just one independent copy of this whole Q, K, V machinery. A model runs many heads in parallel, each learning to look for a different kind of relationship.&lt;/p&gt;

  &lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;dot product&lt;/strong&gt; is a single number that measures how much two lists of numbers agree. You multiply them position by position and add it up, so &lt;code&gt;[1, 2]&lt;/code&gt; and &lt;code&gt;[3, 1]&lt;/code&gt; give &lt;code&gt;3 + 2 = 5&lt;/code&gt;. The idea is that a bigger number means the two lists are more alike, which is how a token measures how relevant another token is to it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Softmax&lt;/strong&gt; is a function that converts raw scores into probabilities. It takes any list of numbers like &lt;code&gt;[1, -2, 3]&lt;/code&gt; and turns it into a distribution like &lt;code&gt;[0.12, 0.01, 0.88]&lt;/code&gt;, where every value sits between 0 and 1 and they all sum to 1. Crucially, it preserves the relative order: the highest score stays the highest and the lowest stays the lowest, so nothing gets reshuffled, it just gets rescaled into &quot;how much attention goes to each token.&quot;&lt;/p&gt;
&lt;/blockquote&gt;

  &lt;p&gt;&lt;strong&gt;Step 1: Project into Q, K, V.&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Every token&apos;s embedding vector X(bank) gets multiplied by three learned weight matrices, W&lt;sub&gt;q&lt;/sub&gt;, W&lt;sub&gt;k&lt;/sub&gt;, W&lt;sub&gt;v&lt;/sub&gt;, each of size &lt;code&gt;(embedding dimension × head dimension)&lt;/code&gt;, e.g. in GPT-2, each head&apos;s matrices are &lt;code&gt;(768 × 64)&lt;/code&gt;:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;Query = X · Wq
Key   = X · Wk
Value = X · Wv&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;We calculate the Q, K, V vectors for every token in the context.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Step 2: Compute attention scores&lt;/strong&gt;: take the dot product of each Query with every Key to get a score matrix:&lt;/p&gt;

  &lt;table class=&quot;matrix-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
&lt;th class=&quot;corner&quot;&gt;Q \ K&lt;/th&gt;
&lt;th&gt;her&lt;/th&gt;
&lt;th&gt;investment&lt;/th&gt;
&lt;th&gt;bank&lt;/th&gt;
&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td class=&quot;rowlabel&quot;&gt;her&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td class=&quot;rowlabel&quot;&gt;investment&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td class=&quot;rowlabel&quot;&gt;bank&lt;/td&gt;
&lt;td class=&quot;followed&quot;&gt;1&lt;/td&gt;
&lt;td class=&quot;followed&quot;&gt;3&lt;/td&gt;
&lt;td class=&quot;followed&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
  &lt;p class=&quot;note&quot;&gt;The highlighted row is the one we follow, the Query &quot;bank&quot; against every Key.&lt;/p&gt;

  &lt;p&gt;For “bank”, the raw scores are &lt;code&gt;[1, 3, 1]&lt;/code&gt;: it aligns strongly with “investment”, weakly with “her” and with itself.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Step 3: Scale&lt;/strong&gt;: these scores can get large, so we divide by √(d&lt;sub&gt;k&lt;/sub&gt;), the square root of the head dimension — the length of each Q/K/V vector. In this toy example each head is 3-dimensional, so d&lt;sub&gt;k&lt;/sub&gt; = 3 and √(d&lt;sub&gt;k&lt;/sub&gt;) = √3 = 1.73. This keeps gradients stable.&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;[1, 3, 1]  → ÷ √(d&lt;sub&gt;k&lt;/sub&gt;) = ÷√3 →  [0.57, 1.73, 0.57]&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;&lt;strong&gt;Step 4: Convert to probabilities&lt;/strong&gt;: run the scaled scores through softmax so they sum to 1.&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;[0.57, 1.73, 0.57]  → softmax →  [0.2, 0.6, 0.2]&lt;/code&gt;&lt;/pre&gt;

  &lt;figure&gt;
  &lt;img src=&quot;/assets/how-llms-work/attention-weights.svg&quot; alt=&quot;Attention weight matrix for her investment bank, with future tokens masked&quot; style=&quot;max-width: 70%;&quot;&gt;
  &lt;figcaption style=&quot;max-width: 70%; margin-left: auto; margin-right: auto;&quot;&gt;Each row shows how one query token splits its attention over the keys it can see; the “bank” row is the &lt;code&gt;[0.2, 0.6, 0.2]&lt;/code&gt; from above&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;&lt;strong&gt;Step 5: Weighted sum of Values&lt;/strong&gt;: multiply each token&apos;s Value vector by its attention weight and sum:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;0.2 × V(&quot;her&quot;) + 0.6 × V(&quot;investment&quot;) + 0.2 × V(&quot;bank&quot;) = new context-aware vector for &quot;bank&quot;&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;The whole thing in one line:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;Attention(Q, K, V) = softmax(Q · Kᵀ / √dk) · V&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Before attention, “bank” was just “bank”, the same vector in a riverbank sentence or a finance one. After, it&apos;s “bank, mostly shaped by ‘investment’,” already leaning financial before the model predicts a single word. The vector wasn&apos;t replaced. It got &lt;strong&gt;nudged&lt;/strong&gt; toward the meaning its neighbors imply.&lt;/p&gt;

  &lt;h3 id=&quot;causal-masking-no-peeking&quot;&gt;Causal Masking (No peeking)&lt;/h3&gt;

  &lt;p&gt;There&apos;s one flaw in Attention. A token may only &lt;strong&gt;attend&lt;/strong&gt; to itself and what came before it. Otherwise it&apos;s cheating. We enforce this by setting future scores to negative infinity before softmax (e&lt;sup&gt;-∞&lt;/sup&gt; = 0), which makes their probability zero:&lt;/p&gt;

  &lt;table class=&quot;matrix-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
&lt;th class=&quot;corner&quot;&gt;Q \ K&lt;/th&gt;
&lt;th&gt;her&lt;/th&gt;
&lt;th&gt;investment&lt;/th&gt;
&lt;th&gt;bank&lt;/th&gt;
&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td class=&quot;rowlabel&quot;&gt;her&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td class=&quot;masked&quot;&gt;−∞&lt;/td&gt;
&lt;td class=&quot;masked&quot;&gt;−∞&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td class=&quot;rowlabel&quot;&gt;investment&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td class=&quot;masked&quot;&gt;−∞&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td class=&quot;rowlabel&quot;&gt;bank&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

  &lt;p&gt;“her” sees only itself. “investment” sees “her” and itself. “bank” sees everything before it. &lt;strong&gt;Every token predicts using only the past&lt;/strong&gt;, exactly as it will when generating for real.&lt;/p&gt;

  &lt;h3 id=&quot;multi-head-attention&quot;&gt;Multi-head attention&lt;/h3&gt;

  &lt;p&gt;One head learns one kind of relationship. Language has many at once, so we run several in parallel, each with its own W&lt;sub&gt;q&lt;/sub&gt;, W&lt;sub&gt;k&lt;/sub&gt;, W&lt;sub&gt;v&lt;/sub&gt;, each hunting a different pattern.&lt;/p&gt;

  &lt;p&gt;The trick is to split the vector into equal chunks, one per head, instead of handing each head the full width. In GPT-2, for example, a 768-dimension embedding vector X splits across 12 heads, taking 64 dimensions each.&lt;/p&gt;

  &lt;p&gt;Take a 4-dimension X(bank) vector with 2 heads:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;&quot;bank&quot;: [0.9, 0.1, 0.8, 0.2]
Head 1: [0.9, 0.1]      Head 2: [0.8, 0.2]&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Each head runs the five steps above on its own slice. One might lean on grammar (“bank” is a singular subject), the other on meaning (“bank” is related to finance). Say they come back with:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;Head 1 → [0.67, 0.67]
Head 2 → [0.16, 0.58]&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Glue them back together, up to full width again (4 here):&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;[0.67, 0.67, 0.16, 0.58]&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;One last matrix, &lt;strong&gt;W&lt;sub&gt;o&lt;/sub&gt;&lt;/strong&gt;, mixes the glued heads together to get the final result.&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;[0.67, 0.67, 0.16, 0.58] × Wo = [0.8, 1.1, 0.4, 0.9]   ← final attention output for &quot;bank&quot;&lt;/code&gt;&lt;/pre&gt;

  &lt;h2 id=&quot;feed-forward-network-mlp&quot;&gt;4. Feed Forward Network (MLP)&lt;/h2&gt;

  &lt;p&gt;The next block is the &lt;strong&gt;MLP&lt;/strong&gt; (or feed-forward network), this is the part where LLMs store facts. In the previous step, the token ‘bank’ gathered context from the surrounding tokens, but it still hasn&apos;t reasoned through it.&lt;/p&gt;

  &lt;p&gt;Knowing “river is near” and “bank is near” isn&apos;t the same as concluding “this is geography.” To reason over the fact that river + bank = riverbank, models need a thinking layer, and that&apos;s where MLPs help.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;MLP stands for multilayer perceptron.&lt;/strong&gt; In this layer, each token&apos;s embedding vector goes through a series of operations where it expands to a larger dimension first and then compresses back to its initial dimension.&lt;/p&gt;

  &lt;p&gt;Unlike the previous step where each token ‘attends’ to one another, in this step &lt;strong&gt;each token thinks on its own&lt;/strong&gt;. No token talks to another here; they all run the same operation in parallel.&lt;/p&gt;

  &lt;p&gt;Here&apos;s the working:&lt;/p&gt;

  &lt;ol&gt;
  &lt;li&gt;
&lt;strong&gt;Expand (W&lt;sub&gt;1&lt;/sub&gt;).&lt;/strong&gt; Blow the vector up into a bigger space (GPT-2 goes 768 → 3072) by multiplying it with the W&lt;sub&gt;1&lt;/sub&gt; matrix.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Apply a nonlinearity&lt;/strong&gt; like GeLU, ReLU, etc.&lt;br&gt;The simplest nonlinear function example is &lt;strong&gt;ReLU&lt;/strong&gt;, which just zeroes out negatives and passes positives through: &lt;code&gt;[0.8, 1.4, -0.2, 2.1]&lt;/code&gt; (ReLU) → &lt;code&gt;[0.8, 1.4, 0, 2.1]&lt;/code&gt;
&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Compress (W&lt;sub&gt;2&lt;/sub&gt;).&lt;/strong&gt; Multiply by the W&lt;sub&gt;2&lt;/sub&gt; matrix to shrink back to the original size.&lt;/li&gt;
&lt;/ol&gt;

  &lt;p&gt;Mathematically:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;MLP(x) = W2 · GELU(W1x + b1) + b2&lt;/code&gt;&lt;/pre&gt;

  &lt;figure&gt;
  &lt;img src=&quot;/assets/how-llms-work/mlp-v1.svg&quot; alt=&quot;MLP: input vector expands via W1, a nonlinearity turns neurons on or off, then W2 compresses back&quot;&gt;
  &lt;figcaption&gt;W&lt;sub&gt;1&lt;/sub&gt; expands the vector, the nonlinearity switches neurons on/off, W&lt;sub&gt;2&lt;/sub&gt; compresses back&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;A nice way to read it: each row of W&lt;sub&gt;1&lt;/sub&gt; asks one question (“is this river + bank?”). The answer after the nonlinearity is a &lt;strong&gt;neuron&lt;/strong&gt;: positive for yes, zero for no. Each column of W&lt;sub&gt;2&lt;/sub&gt; is the response to add back when that neuron fires. When “riverbank” lights up, W&lt;sub&gt;2&lt;/sub&gt; stamps “geography, water, landscape” onto the token.&lt;/p&gt;

  &lt;p&gt;This is also where &lt;strong&gt;facts live&lt;/strong&gt;. Most of a model&apos;s parameters sit in these layers, not in attention, and they aren&apos;t generic bookkeeping. Researchers have found single neurons that fire on the Eiffel Tower, or on past-tense verbs, or on a specific programming language. When a model “knows” Paris is the capital of France, that fact is spread across FFN weights in particular layers.&lt;/p&gt;

  &lt;h2 id=&quot;layernorm-and-residuals&quot;&gt;5. LayerNorm and Residuals&lt;/h2&gt;

  &lt;p&gt;Two pieces of plumbing keep a deep stack trainable.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;LayerNorm.&lt;/strong&gt; After a few blocks, a token&apos;s numbers drift. Some hit 500, others 0.001. Softmax hates that (&lt;code&gt;[500, 501, 499]&lt;/code&gt; collapses to nearly &lt;code&gt;[0, 1, 0]&lt;/code&gt;), and training goes unstable. LayerNorm rescales each token&apos;s vector to mean 0 and spread 1, keeping the pattern but taming the scale. &lt;code&gt;[10, 2, 6, 14]&lt;/code&gt; becomes &lt;code&gt;[0.45, -1.34, -0.45, 1.34]&lt;/code&gt; Notice how the relative pattern is preserved but the scale is tamed.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Residuals.&lt;/strong&gt; Each block adds its output back onto its input instead of replacing it:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;new = old + block(old)&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Without this, a later block can wipe out what an earlier one figured out, and “bank” could forget it was ever “bank.” With it, the original rides along and each block only has to add a small nudge. A block with nothing useful to contribute can output roughly 0 and get skipped for free.&lt;/p&gt;

  &lt;p&gt; For instance, “cat”&apos;s vector is &lt;code&gt;[0.8, 0.3]&lt;/code&gt; and attention produces the update &lt;code&gt;[0.0, 1.1]&lt;/code&gt;:&lt;/p&gt;

  &lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Without residual:&lt;/strong&gt; new vector = &lt;code&gt;[0.0, 1.1]&lt;/code&gt;, the &lt;code&gt;0.8&lt;/code&gt; is gone forever, unrecoverable by later blocks.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;With residual:&lt;/strong&gt; new vector = &lt;code&gt;[0.8, 0.3] + [0.0, 1.1] = [0.8, 1.4]&lt;/code&gt;, the original survives, new context layered on top.&lt;/li&gt;
&lt;/ul&gt;

  &lt;p&gt;Residuals also save training. A deep stack has to send a correction signal backward from the last block to the first, and without a shortcut that signal fades to nothing on the way. This is the &lt;strong&gt;vanishing gradient problem.&lt;/strong&gt; The residual is a clean path straight through, so early layers still learn. More on that in part 2.&lt;/p&gt;

  &lt;p&gt; LayerNorm and Residuals are added after each block to keep the model trainable.&lt;/p&gt;

  &lt;h2 id=&quot;the-full-forward-pass&quot;&gt;6. The Full Forward Pass&lt;/h2&gt;

  &lt;p&gt;We&apos;ve built every piece on its own. Now let&apos;s wire them together and watch a single token make the whole trip.&lt;/p&gt;

  &lt;p&gt;One &lt;strong&gt;forward pass&lt;/strong&gt; is just text going in one end and a prediction coming out the other. Tokenize the text, look up each token&apos;s meaning and position, then push the stack through the transformer blocks: attention lets the tokens read each other, the MLP reasons over what they gathered, and LayerNorm and residuals keep the whole thing stable. That block repeats over and over (12 times in GPT-2), each pass layering a little more context onto every token.&lt;/p&gt;

  &lt;figure&gt;
  &lt;img src=&quot;/assets/how-llms-work/pipeline.svg&quot; alt=&quot;End-to-end LLM pipeline: text, tokenizer, token IDs, embedding lookup, positional encoding, transformer block repeated N times, unembedding, softmax, next token&quot; style=&quot;width: 100%; max-width: 100%; max-height: none;&quot;&gt;
  &lt;figcaption&gt;The full path from text to the next token, every stage of this post in one picture&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;We&apos;ve followed a token all the way from tokenization through the transformer blocks. Out the far end comes a refined vector &lt;strong&gt;X′&lt;/strong&gt;, loaded with all the context it gathered along the way. But X′ is still just a list of numbers. We need to turn it back into words and predict the next token, the job we started with. Three steps do it:&lt;/p&gt;

  &lt;ol&gt;
  &lt;li&gt;
&lt;strong&gt;A final LayerNorm.&lt;/strong&gt; One last rescale to tidy up the vector. We&apos;re still left with a 768-dimensional embedding vector (in GPT-2), the same shape we started with back in the embedding step.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;The language-model head (LM head).&lt;/strong&gt; To get from a vector back to words, we do the reverse of embedding: embedding turned a token ID into a vector, now we turn a vector into a score for every token in the vocabulary. We don&apos;t even need a brand-new matrix, the head is usually the &lt;strong&gt;embedding matrix (WTE) transposed&lt;/strong&gt;, a &lt;code&gt;(768 × vocab)&lt;/code&gt; matrix reused in reverse. Multiplying X′ by it gives one raw score per token. Those scores are the &lt;strong&gt;logits&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Softmax.&lt;/strong&gt; Turn the logits into probabilities that sum to 1. That&apos;s the probability table from the top of the post.&lt;/li&gt;
&lt;/ol&gt;

  &lt;p&gt;Back to our sentence, “She called her investment ___”. The LM head turns X′ into one logit per token in the vocabulary, and softmax turns those into probabilities:&lt;/p&gt;

  &lt;table class=&quot;matrix-table&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Logit&lt;/th&gt;
&lt;th&gt;Probability&lt;/th&gt;
&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
&lt;td class=&quot;followed&quot;&gt;bank&lt;/td&gt;
&lt;td class=&quot;followed&quot;&gt;3.4&lt;/td&gt;
&lt;td class=&quot;followed&quot;&gt;0.61&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;firm&lt;/td&gt;
&lt;td&gt;2.4&lt;/td&gt;
&lt;td&gt;0.23&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;advisor&lt;/td&gt;
&lt;td&gt;1.6&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;account&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;loan&lt;/td&gt;
&lt;td&gt;-0.2&lt;/td&gt;
&lt;td&gt;0.02&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
&lt;td&gt;broker&lt;/td&gt;
&lt;td&gt;-0.4&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

  &lt;p&gt;The model is most confident the next word is &lt;strong&gt;bank&lt;/strong&gt;. One logit per token, squashed into one probability per token, and the highest one is the model&apos;s best guess. (The names model works the same way, just with &lt;code&gt;vocab = 27&lt;/code&gt;, one score for each letter a to z plus the end token.)&lt;/p&gt;

  &lt;p&gt;Here&apos;s the whole forward pass in code:&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;def gpt(token_ids, positions):
    x = wte[token_ids] + wpe[positions]   # embed: what + where

    for block in blocks:                  # 12 blocks in GPT-2
        x = x + attn(layer_norm(x))       # gather context (+ residual)
        x = x + mlp(layer_norm(x))        # reason over it (+ residual)

    x = layer_norm_final(x)
    return lm_head(x)                     # logits over the vocabulary&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;That&apos;s the forward pass. Embed the tokens, alternate attention and MLP a dozen times, project back to the vocabulary. Every section above is one line here.&lt;/p&gt;

  &lt;h2 id=&quot;inference&quot;&gt;Inference&lt;/h2&gt;

  &lt;p&gt;The model scores every position at once, but when generating we only care about the last one: the prediction for the next token.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Generation is a loop.&lt;/strong&gt; Predict a token, stick it on the end, run again.&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;&quot;She called her investment&quot; → &quot;bank&quot;
&quot;She called her investment bank&quot; → &quot;about&quot;
...&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;How you pick from the distribution is a choice:&lt;/p&gt;

  &lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Greedy&lt;/strong&gt;: always take the top token. Deterministic, often dull.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Temperature&lt;/strong&gt;: divide the logits by T before softmax. Below 1 sharpens the odds (safer), above 1 flattens them (wilder).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Top-k / top-p&lt;/strong&gt;: sample only from the top k tokens, or from the smallest set of tokens covering probability p.&lt;/li&gt;
&lt;/ul&gt;

  &lt;figure&gt;
&lt;svg viewbox=&quot;0 0 720 380&quot; role=&quot;img&quot; aria-label=&quot;The inference loop: input tokens go through the GPT forward pass to predict the next token; if it is the end-of-sequence token the loop stops, otherwise the token is appended and the loop repeats&quot; style=&quot;width:100%;height:auto;max-width:680px;display:block;margin:1.4em auto;font-family:-apple-system,BlinkMacSystemFont,&apos;Segoe UI&apos;,Helvetica,Arial,sans-serif;&quot;&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;ilArrow&quot; viewbox=&quot;0 0 10 10&quot; refx=&quot;9&quot; refy=&quot;5&quot; markerwidth=&quot;7&quot; markerheight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt;
      &lt;path d=&quot;M0,0 L10,5 L0,10 z&quot; fill=&quot;#555&quot;&gt;&lt;/path&gt;
    &lt;/marker&gt;
  &lt;/defs&gt;

  &lt;rect x=&quot;1&quot; y=&quot;1&quot; width=&quot;718&quot; height=&quot;378&quot; rx=&quot;16&quot; fill=&quot;#fbfbfa&quot; stroke=&quot;#e2e2e2&quot;&gt;&lt;/rect&gt;

  &lt;!-- 1. Input --&gt;
  &lt;rect x=&quot;45&quot; y=&quot;52&quot; width=&quot;130&quot; height=&quot;56&quot; rx=&quot;10&quot; fill=&quot;#fff&quot; stroke=&quot;#2f5d9f&quot; stroke-width=&quot;1.5&quot;&gt;&lt;/rect&gt;
  &lt;text x=&quot;110&quot; y=&quot;76&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#9a9a9a&quot; font-weight=&quot;600&quot;&gt;1. INPUT&lt;/text&gt;
  &lt;text x=&quot;110&quot; y=&quot;96&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;#1a1a1a&quot;&gt;tokens&lt;/text&gt;

  &lt;!-- 2. GPT block --&gt;
  &lt;rect x=&quot;255&quot; y=&quot;52&quot; width=&quot;150&quot; height=&quot;56&quot; rx=&quot;10&quot; fill=&quot;#eaf1fb&quot; stroke=&quot;#2f5d9f&quot; stroke-width=&quot;1.5&quot;&gt;&lt;/rect&gt;
  &lt;text x=&quot;330&quot; y=&quot;76&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#2f5d9f&quot; font-weight=&quot;600&quot;&gt;2. GPT BLOCK&lt;/text&gt;
  &lt;text x=&quot;330&quot; y=&quot;96&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;#1a1a1a&quot;&gt;forward pass&lt;/text&gt;

  &lt;!-- 3. Output / next token --&gt;
  &lt;rect x=&quot;480&quot; y=&quot;52&quot; width=&quot;160&quot; height=&quot;56&quot; rx=&quot;10&quot; fill=&quot;#fff&quot; stroke=&quot;#2f5d9f&quot; stroke-width=&quot;1.5&quot;&gt;&lt;/rect&gt;
  &lt;text x=&quot;560&quot; y=&quot;76&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#9a9a9a&quot; font-weight=&quot;600&quot;&gt;3. OUTPUT&lt;/text&gt;
  &lt;text x=&quot;560&quot; y=&quot;96&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;#1a1a1a&quot;&gt;next token&lt;/text&gt;

  &lt;!-- EOS decision diamond --&gt;
  &lt;polygon points=&quot;560,158 622,200 560,242 498,200&quot; fill=&quot;#fff7e6&quot; stroke=&quot;#c69214&quot; stroke-width=&quot;1.5&quot;&gt;&lt;/polygon&gt;
  &lt;text x=&quot;560&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;#1a1a1a&quot; font-weight=&quot;600&quot;&gt;EOS?&lt;/text&gt;

  &lt;!-- Stop --&gt;
  &lt;rect x=&quot;490&quot; y=&quot;292&quot; width=&quot;140&quot; height=&quot;52&quot; rx=&quot;10&quot; fill=&quot;#e9f5ee&quot; stroke=&quot;#2f7d5f&quot; stroke-width=&quot;1.5&quot;&gt;&lt;/rect&gt;
  &lt;text x=&quot;560&quot; y=&quot;323&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;#2f7d5f&quot; font-weight=&quot;600&quot;&gt;4. Stop&lt;/text&gt;

  &lt;!-- arrows: main flow --&gt;
  &lt;line x1=&quot;175&quot; y1=&quot;80&quot; x2=&quot;249&quot; y2=&quot;80&quot; stroke=&quot;#555&quot; stroke-width=&quot;1.6&quot; marker-end=&quot;url(#ilArrow)&quot;&gt;&lt;/line&gt;
  &lt;line x1=&quot;405&quot; y1=&quot;80&quot; x2=&quot;474&quot; y2=&quot;80&quot; stroke=&quot;#555&quot; stroke-width=&quot;1.6&quot; marker-end=&quot;url(#ilArrow)&quot;&gt;&lt;/line&gt;
  &lt;line x1=&quot;560&quot; y1=&quot;108&quot; x2=&quot;560&quot; y2=&quot;152&quot; stroke=&quot;#555&quot; stroke-width=&quot;1.6&quot; marker-end=&quot;url(#ilArrow)&quot;&gt;&lt;/line&gt;

  &lt;!-- yes -&gt; stop --&gt;
  &lt;line x1=&quot;560&quot; y1=&quot;242&quot; x2=&quot;560&quot; y2=&quot;286&quot; stroke=&quot;#555&quot; stroke-width=&quot;1.6&quot; marker-end=&quot;url(#ilArrow)&quot;&gt;&lt;/line&gt;
  &lt;text x=&quot;574&quot; y=&quot;270&quot; font-size=&quot;13&quot; fill=&quot;#2f7d5f&quot; font-weight=&quot;600&quot;&gt;Yes&lt;/text&gt;

  &lt;!-- no -&gt; loop back to input --&gt;
  &lt;polyline points=&quot;498,200 110,200 110,114&quot; fill=&quot;none&quot; stroke=&quot;#555&quot; stroke-width=&quot;1.6&quot; marker-end=&quot;url(#ilArrow)&quot;&gt;&lt;/polyline&gt;
  &lt;text x=&quot;300&quot; y=&quot;192&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#c26a1b&quot; font-weight=&quot;600&quot;&gt;No — append token &amp;amp; repeat&lt;/text&gt;
&lt;/svg&gt;
&lt;figcaption style=&quot;max-width: 680px; margin-left: auto; margin-right: auto;&quot;&gt;The inference loop: run the forward pass, take the predicted token, stop on EOS, otherwise append it and run again&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;p&gt;You stop when the model emits EOS, or when you hit a length cap. The names model samples letters (&lt;code&gt;s&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;m&lt;/code&gt;) until it emits the end token, and you&apos;ve got a fresh name.&lt;/p&gt;

  &lt;h2 id=&quot;what-next&quot;&gt;What Next?&lt;/h2&gt;

  &lt;p&gt;It took decades of breakthroughs in computer science to finally build AI, and there&apos;s still so much left to do. What we covered here is just the foundation, there&apos;s a lot more to how LLMs work, at scale and in optimization, that I didn&apos;t get into. But hopefully these basics open up the vast ocean of things you can now go learn from.&lt;/p&gt;

  &lt;p&gt;The best next step is to see all of this in code. Go read &lt;a href=&quot;https://karpathy.github.io/2026/02/12/microgpt/&quot; target=&quot;_blank&quot;&gt;microGPT&lt;/a&gt;, the whole thing running in a couple hundred lines of Python.&lt;/p&gt;

  &lt;p&gt;If you found this helpful, reach out on &lt;a href=&quot;https://x.com/sambharia&quot; target=&quot;_blank&quot;&gt;X&lt;/a&gt;. I&apos;d love your feedback, and I love making new friends.&lt;/p&gt;

  &lt;p&gt;This post was inspired by &lt;a href=&quot;https://karpathy.github.io/2026/02/12/microgpt/&quot; target=&quot;_blank&quot;&gt;Karpathy&apos;s microGPT&lt;/a&gt; and &lt;a href=&quot;https://karpathy.github.io/&quot; target=&quot;_blank&quot;&gt;blog&lt;/a&gt;, &lt;a href=&quot;https://leerob.com/ai&quot; target=&quot;_blank&quot;&gt;Lee Robinson&apos;s Understanding AI&lt;/a&gt;, and &lt;a href=&quot;https://www.0xkato.xyz/how-llms-actually-work/&quot; target=&quot;_blank&quot;&gt;0xkato&apos;s How LLMs Actually Work&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;

&lt;style&gt;
  .how-llms-work-post .post-header {
    width: 100%;
    max-height: 340px;
    object-fit: cover;
    border-radius: 10px;
    margin: 0 0 1.8em;
  }
  .how-llms-work-post .toc {
    background: #f6f7ff;
    border: 1px solid #e0e2fb;
    border-left: 3px solid #2a2ecd;
    border-radius: 8px;
    padding: 20px 24px 20px 22px;
  }
  .how-llms-work-post .toc ol { margin: 0; padding-left: 1.2em; }
  .how-llms-work-post .toc ol li::marker { color: #2a2ecd; }
  .how-llms-work-post table {
    border-collapse: collapse;
    width: 100%;
    margin: 1.5em 0;
    font-size: 0.95em;
  }
  .how-llms-work-post th,
  .how-llms-work-post td {
    border: 1px solid #e2e2e2;
    padding: 8px 12px;
    text-align: center;
  }
  .how-llms-work-post th {
    background: #f0f0ee;
    font-weight: 600;
  }
  .how-llms-work-post td.rowlabel,
  .how-llms-work-post th.corner {
    background: #f7f7f5;
    font-weight: 600;
  }
  .how-llms-work-post .matrix-table td.masked {
    color: #b5b5b5;
  }
  .how-llms-work-post .matrix-table td.followed {
    background: #fff4d6;
    font-weight: 600;
  }
  .how-llms-work-post .note {
    font-size: 0.9em;
    color: #555;
  }
  .how-llms-work-post figcaption {
    text-align: center;
    font-size: 0.85em;
    color: #555;
    margin-top: -0.6em;
    margin-bottom: 1.4em;
  }
  .how-llms-work-post pre {
    background: #f4f4f2;
    border: 1px solid #e2e2e2;
    border-radius: 8px;
    padding: 16px 18px;
    overflow-x: auto;
    font-size: 0.92em;
  }
  .how-llms-work-post pre code {
    background: none;
    padding: 0;
  }
&lt;/style&gt;

</content>
    <category term="ai" /><category term="llms" />
  </entry>
  
  <entry>
    <title>Why You Should Learn How AI Actually Works</title>
    <link href="https://sambharia.com/learn-ai" rel="alternate" type="text/html" title="Why You Should Learn How AI Actually Works" />
    <id>https://sambharia.com/learn-ai</id>
    <published>2026-07-08T00:00:00+00:00</published>
    <updated>2026-07-08T00:00:00+00:00</updated>
    <summary type="html">AI is a fascinating technology. I still remember the first time I tried the Davinci model in 2020 from OpenAI, and I was flabbergasted by how it could artificially write blocks of meaningful text.</summary>
    <content type="html">&lt;p&gt;AI is a fascinating technology. I still remember the first time I tried the Davinci model in 2020 from OpenAI, and I was flabbergasted by how it could artificially write blocks of meaningful text.&lt;/p&gt;

&lt;p&gt;Since then a lot has changed. We had ChatGPT in 2022, GPT-4 in 2023, Claude Code in 2025, OpenClaw in 2026, to now whatever is the current track of &lt;a href=&quot;https://www.ai.engineer/&quot; target=&quot;_blank&quot;&gt;AI Engineer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was born in 2003, by the time I got to university all the biggest technological revolutions of the last decade – the internet, the iPhone, cloud were past their inflection point. But AI is different, it’s the only &lt;a href=&quot;https://en.wikipedia.org/wiki/General-purpose_technology&quot; target=&quot;_blank&quot;&gt;General-purpose technologies&lt;/a&gt; that I’ve witnessed from the launch of ChatGPT to becoming fastest growing consumer technology ever created.&lt;/p&gt;

&lt;h2 id=&quot;broken-fundamentals&quot;&gt;Broken Fundamentals&lt;/h2&gt;

&lt;p&gt;I am much more AI-pilled than 99% of people out there and been part of this ecosystem since its infancy (building a GPT wrapper in 23, &lt;a href=&quot;https://portkey.ai&quot; target=&quot;_blank&quot;&gt;AI gateway&lt;/a&gt; 24-26). But I was so busy with the next ‘shiny’ thing in AI that I never took out time to learn the fundamentals until now.&lt;/p&gt;

&lt;p&gt;The problem with this approach chasing the next shiny thing is that you are building a skyscraper with a broken foundation.&lt;/p&gt;

&lt;h2 id=&quot;why-learn-the-basics-of-ai&quot;&gt;Why Learn The Basics of AI?&lt;/h2&gt;

&lt;p&gt;Fair question. For most technology you can simply use it without understanding how it works. I don’t need to understand how airplanes work, or how their microwave heats food, and the same goes for 90% of all the consumer technology out there.&lt;/p&gt;

&lt;p&gt;Here’s why I believe understanding AI matters:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;There’s so much junk out there with AI; for you to decide what’s useful you need the taste that can be developed once you understand the basics of this technology.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you are a builder, a good way you develop new ideas is by being at the frontier of technology, understand what it’s capable of doing. Being on the frontier allows you to see the blindspots and build tools to solve them. In return capture value to build wealth. Unless you know how LLMs work, you won’t be able to understand what you can do with them, to mine gold or sell shovels.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;AI is a force multiplier. People who fully embrace a new technology aren’t 5x more productive than the old way of working — they’re 100x. But here’s the paradox: AI, like any technology, isn’t perfect. It makes errors. And the only way to reliably spot those errors is to understand the fundamentals. Do you have an intuition for why image models used to mangle human hands, and how frontier labs fixed it? Or why models struggle to count the R’s in “strawberry”? The basics are what let you answer questions like these.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The learning cost is lowest early. It’s easier to grow along with new technology when it’s early. like reading Darwin’s “On the Origin of Species” is better to understand evolution than reading CRISPR. Paying that learning cost while the AI is still young lets you hill climb faster.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;

&lt;p&gt;This is an exciting time to build. Like every technology wave before it, this is my attempt to document what I’m learning in the open — sharing notes, and along the way meeting new people, running into new ideas, and earning some distribution.&lt;/p&gt;

&lt;p&gt;I’ll post once a week: fundamentals, what’s new, and whatever I can’t stop thinking about.
Next up: &lt;a href=&quot;https://sambharia.com/how-llms-work&quot; target=&quot;_blank&quot;&gt;how LLMs work at a fundamental level&lt;/a&gt;.&lt;/p&gt;

</content>
    <category term="ai" />
  </entry>
  
  <entry>
    <title>A Brief History of AI Agents</title>
    <link href="https://sambharia.com/brief-history-of-ai-agents" rel="alternate" type="text/html" title="A Brief History of AI Agents" />
    <id>https://sambharia.com/brief-history-of-ai-agents</id>
    <published>2026-04-25T00:00:00+00:00</published>
    <updated>2026-04-26T00:00:00+00:00</updated>
    <summary type="html">Agents</summary>
    <content type="html">&lt;p class=&quot;post-kicker&quot;&gt;Agents&lt;/p&gt;

&lt;p class=&quot;post-lede&quot;&gt;From chat completions API to agent harnesses and everything in between.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/cover-image.jpeg&quot; alt=&quot;Cover&quot;&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;I work at @PortkeyAI , where my job is to play with the latest AI tools and figure out how they fit into our gateway. Since ChatGPT launched in late 2022, I’ve watched every abstraction people call an “agent” become outdated within months as models got stronger. Once in a while it’s worth stepping back to see how we actually got here.&lt;/p&gt;

&lt;h2 id=&quot;timeline-tldr&quot;&gt;Timeline (TL;DR)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Part 1&lt;/strong&gt; the cognitive revolution - Chat Completions&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Part 2&lt;/strong&gt; models get hands - Function Calling&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Part 3&lt;/strong&gt; models discover autonomy - Agent Loop&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Part 4&lt;/strong&gt; models get a body - Harness&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Part 5&lt;/strong&gt; the outer loop - Ralph Loops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;every AI product today is a language model wrapped in scaffolding. the model is a line in a config file. obvious now. wasn’t obvious in 2022.&lt;/p&gt;

&lt;p&gt;this is five timeline on how we started from chat completions API all the way upto claude code. each part adds one capability the model couldn’t do the part before.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(the pre-LLM history of software orchestration, DAG schedulers, ML pipelines, is covered well in @dexhorthy’s 12-factor agents. this post picks up where language models enter the picture.)&lt;/em&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;h2 id=&quot;part-1--the-cognitive-revolution-2020--early-2023&quot;&gt;part 1 — the cognitive revolution (2020 – early 2023)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/part-1.jpeg&quot; alt=&quot;Part 1 — the cognitive revolution&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-api--chat-completions&quot;&gt;💡the API — chat completions&lt;/h2&gt;

&lt;p&gt;for most of its history, a language model was a research artifact. you could read the paper, maybe download the weights if you had the right GPU, run it in a notebook. but you couldn’t call it. it lived behind lab doors.&lt;/p&gt;

&lt;p&gt;that changed on june 11, 2020, when OpenAI released an API for GPT-3. the announcement was a single paragraph that now reads like a founding stone:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;We’re releasing an API for accessing new AI models developed by OpenAI. Unlike most AI systems which are designed for one use-case, the API today provides a general-purpose “text in, text out” interface, allowing users to try it on virtually any English language task.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;“text in, text out.” you sent a prompt, you got a completion back. no memory between calls. no ability to act on the world. one forward pass and done.&lt;/p&gt;

&lt;p&gt;the only lever was the prompt itself, and an entire discipline, prompt engineering, grew up around this single knob. few-shot examples, chain-of-thought, role-playing instructions — all attempts to squeeze more capability out of a system that could only read text and write text.&lt;/p&gt;

&lt;p&gt;this primitive, the single completion call, never goes away. every part that follows wraps more machinery around it.&lt;/p&gt;

&lt;h2 id=&quot;chaining-calls&quot;&gt;💡chaining calls&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/part-1.2.jpeg&quot; alt=&quot;Part 1.2 — chaining calls&quot;&gt;&lt;/p&gt;

&lt;p&gt;one call wasn’t always enough. ask a model to write a blog post in a single prompt and you’d get something unfocused, or wrong in a way that was hard to fix without starting over.&lt;/p&gt;

&lt;p&gt;the fix was obvious: break the task into steps. first call generates a plan. second call implements it. third call verifies the output. wire them in order, branch on failure.&lt;/p&gt;

&lt;p&gt;harrison chase packaged this in october 2022 with LangChain. ChatGPT launched a month later, millions of developers wanted to build with language models, and LangChain was the fastest on-ramp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the developer decides the control flow. the model fills the nodes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;this is all workflow territory. the first real takeaway: if your task breaks down into steps you can define in advance, a workflow beats an agent almost every time.&lt;/p&gt;

&lt;hr&gt;

&lt;h2 id=&quot;part-2--models-get-hands-mid-2023&quot;&gt;Part 2 — models get hands (mid 2023)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/part-2.jpeg&quot; alt=&quot;Part 2 — models get hands&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;function-calling&quot;&gt;💡function calling&lt;/h2&gt;

&lt;p&gt;everything so far had a limitation so fundamental it was easy to miss: the model could only talk. it couldn’t check a database, call an API, run a calculation, or touch the outside world.&lt;/p&gt;

&lt;p&gt;on june 13, 2023, OpenAI shipped function calling. Anthropic followed with tool use in early 2024. alongside your prompt, you send a list of tools described as JSON schemas. instead of answering in plain text, the model can reply with a structured call like get_weather(city=”mumbai”). your code runs the function, and the result goes back to the model.&lt;/p&gt;

&lt;p&gt;thorsten ball has a good way of explaining what this actually is. imagine telling a friend: “in our conversation, wink if you want me to raise my arm.” weird instruction, but easy to follow. function calling is the same idea, just formalized. you tell the model what tools exist. when it wants to use one, it winks. you execute. you reply with the result.&lt;/p&gt;

&lt;p&gt;under the hood, it’s still just text — the model generates a JSON blob instead of a sentence. but conceptually, the model was no longer sealed off. any capability you could wrap in a function became something it could reach for.&lt;/p&gt;

&lt;p&gt;most early tool use was single-shot: one function, one result, one response. the model had hands. someone else was still guiding them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;source: OpenAI — function calling and other API updates&lt;/em&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;h2 id=&quot;part-3--models-discover-autonomy-late-2023--2024&quot;&gt;Part 3 — models discover autonomy (late 2023 – 2024)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/part-3.jpeg&quot; alt=&quot;Part 3 — models discover autonomy&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-agent-loop&quot;&gt;💡the agent loop&lt;/h2&gt;

&lt;p&gt;up to this point, a language model was a tool. you called it, it answered, you decided what to ask next.&lt;/p&gt;

&lt;p&gt;the agentic loop inverted this. instead of you deciding the next step, the model decides. call a tool, get the result, feed it back, ask “what now?” repeat until the model says it’s done.&lt;/p&gt;

&lt;p&gt;fifteen lines. ReAct (yao et al., 2022) formalized the pattern. thorsten ball’s how to build an agent proved a working coding agent is about 400 lines once you strip the harness away.&lt;/p&gt;

&lt;p&gt;the shift was in who held the steering wheel. before the loop, you drove. after it, the model drove. you built the car, handed over the keys, and told it where to go.&lt;/p&gt;

&lt;p&gt;anthropic’s &lt;em&gt;building effective agents&lt;/em&gt; guide drew the line: workflows are systems where the developer orchestrates the control flow. agents are systems where the model directs its own process. the loop is where agents begin. and with that shift came a new failure mode: workflows fail like factories, at a known step you can fix. agents fail like people — they get confused, double down, and you have to trace where the reasoning went sideways.&lt;/p&gt;

&lt;h2 id=&quot;the-org-chart-fallacy&quot;&gt;💡the org chart fallacy&lt;/h2&gt;

&lt;p&gt;once the loop worked, the next idea felt natural: if one agent is good, a team should be better. assign roles — planner, researcher, writer, critic — and wire them like an org chart.&lt;/p&gt;

&lt;p&gt;AutoGPT and BabyAGI both launched in march 2023, days after GPT-4. AutoGPT hit the top of GitHub overnight. a whole framework category followed within months: AutoGen, CrewAI, LangGraph, OpenAI Swarm. it felt like the future was a squad of specialized agents collaborating like a high-performing team.&lt;/p&gt;

&lt;p&gt;In 1975, brooks’ &lt;em&gt;the Mythical Man-Month&lt;/em&gt; observed that adding people to a late project makes it later — not because the people are bad, but because coordination has a cost. multi-agent systems hit the same wall. handoffs lost information. debugging meant figuring out which agent, on which turn, made the wrong call.&lt;/p&gt;

&lt;p&gt;teams shipped multi-agent, then quietly went back to one agent with better tools. the frameworks survived for their plumbing: state machines, checkpointing, observability. the idea wasn’t wrong. the shape was. the version that actually works is one boss with disposable interns, not a committee of equals.&lt;/p&gt;

&lt;h2 id=&quot;the-wall&quot;&gt;💡the wall&lt;/h2&gt;

&lt;p&gt;even a well-built single agent starts losing coherence after ten to fifteen turns. it forgets what it tried, repeats itself, wanders. each turn is maybe 90% reliable; over fifteen turns, your odds of a clean run are about 20%.&lt;/p&gt;

&lt;p&gt;longer context windows helped, but didn’t solve it. more autonomy wasn’t the answer. more structure was.&lt;/p&gt;

&lt;hr&gt;

&lt;h2 id=&quot;part-4--models-get-a-body-2024--2026&quot;&gt;Part 4 — models get a body (2024 – 2026)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/part-4.jpeg&quot; alt=&quot;Part 4 — models get a body&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-harness&quot;&gt;💡the harness&lt;/h2&gt;

&lt;p&gt;the wall created a design question. the loop worked. tools worked. but pure autonomy degraded fast. what was missing wasn’t intelligence. it was structure.&lt;/p&gt;

&lt;p&gt;the answer was the harness: every piece of code, configuration, and execution logic that isn’t the model itself. system prompt, toolset, loop logic, context management, permissions, feedback mechanisms. &lt;strong&gt;if you’re not the model, you’re the harness.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;source: @Vtrivedy10&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;the first harnesses that mattered were developer tools. @cursor_ai , @github  Copilot Chat, @cognition’s Devin.  they shared a pattern: narrow domain, opinionated tools, a UX that made the agent’s actions visible to a human.(code was the natural first domain — it’s verifiable and recursively self-improving, what @swyx calls the pareto principle of AGI.)&lt;/p&gt;

&lt;p&gt;Claude Code (Anthropic, 2025) became the canonical example. @trq212  shihipar from the team wrote “seeing like an agent”: you design tools shaped to the model’s abilities, and you learn what those abilities are by watching it work. the tools kept evolving because the assumptions they encoded kept going stale.&lt;/p&gt;

&lt;p&gt;sub-agents follow the same logic. the org chart fallacy failed because it used a committee. Claude Code uses a boss with interns: spawn a sub-instance with a clean context and a narrow question, get a summary back, never see the noise. once Claude Code proved the pattern, open-source alternatives followed: OpenCode, Aider, Pi. the harness is a known shape now. what differs is taste.&lt;/p&gt;

&lt;p&gt;models today are post-trained with their harness in the loop. @swyx paraphrased McLuhan: &lt;em&gt;“first the model shapes the harness, then the harness shapes the model.”&lt;/em&gt; CompileBench and TerminalBench show the same model scoring very differently across harnesses. every harness component encodes an assumption about what the model can’t do, and those assumptions go stale fast.&lt;/p&gt;

&lt;h2 id=&quot;the-open-edge&quot;&gt;💡the open edge&lt;/h2&gt;

&lt;p&gt;Claude Code shipped with a fixed toolset. three things opened the edge.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; (Model Context Protocol, november 2024) standardized how agents connect to external tools. client-server over JSON-RPC: implement once on each side and they talk. OpenAI and Google adopted it; by december 2025 Anthropic donated it to the Linux Foundation.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; (october 2025) are the other half. MCP gives the agent new tools. a skill gives it new knowledge: a SKILL.md that teaches a workflow at runtime. the agent loads context when relevant instead of stuffing everything into the window at startup.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Computer use&lt;/strong&gt; (october 2024) redefined what a tool can be. screenshot, coordinates, click, type. any GUI becomes a tool surface. slower than a native API, but universally applicable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the pattern: a stable harness core and an open edge where capabilities plug in from outside. MCP for tools, skills for workflows, computer use for everything else.&lt;/p&gt;

&lt;hr&gt;

&lt;h2 id=&quot;part-5--the-outer-loop-2025-and-beyond&quot;&gt;Part 5 — the outer loop (2025 and beyond)&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/brief-histor-of-ai-agents/part-5.jpeg&quot; alt=&quot;Part 5 — the outer loop&quot;&gt;&lt;/p&gt;

&lt;p&gt;the agent loop let the model decide what to do next. the harness wrapped that loop in structure. the outer loop does the obvious thing: takes the entire harness and puts it back inside a loop.&lt;/p&gt;

&lt;p&gt;@GeoffreyHuntley’s &lt;strong&gt;ralph wiggum technique&lt;/strong&gt; is the simplest version. named after the Simpsons character because it is persistently optimistic and surprisingly effective, ralph is a bash loop:&lt;/p&gt;

&lt;p&gt;same prompt every iteration. fresh context every iteration. state doesn’t live in the conversation. it lives in the codebase: git commits, test results, a fix_plan.md, an AGENT.md the model updates with what it learns. each iteration reads the current state of the world, picks one thing to do, does it, commits, and exits. the loop restarts with a clean window and a slightly different filesystem.&lt;/p&gt;

&lt;p&gt;huntley ran this for three months and built Cursed Lang, a complete programming language with a self-hosting compiler, for a language that didn’t exist in the model’s training data. dex at HumanLayer tested it for refactoring, leaving it overnight against a coding standards doc. his takeaway: code is cheap. the prompt is what matters.&lt;/p&gt;

&lt;p&gt;the same pattern works beyond code. @karpathy’s &lt;strong&gt;autoresearch&lt;/strong&gt; puts an agent in a loop against a training run: propose a change, evaluate against validation loss, plan the next experiment, repeat. he ran it for two days against a project he’d already hand-tuned extensively. the agent found twenty real improvements he’d missed.&lt;/p&gt;

&lt;p&gt;deep research products from Anthropic, OpenAI, and Google are the polished version. ralph builds code. autoresearch tunes models. deep research writes reports. different domains, same primitive: a loop that calls a harness, evaluates the result, and decides whether to keep going.&lt;/p&gt;

&lt;p&gt;every layer compensated for a weakness. as models got better, some of it got absorbed. the prompt hacks from 2022 are unnecessary now. the multi-agent frameworks from 2023 already look ornate. the 2025 harness probably will too by 2028.&lt;/p&gt;

&lt;p&gt;if you’re building today: start with a workflow. reach for the agentic loop only when you need runtime decisions. keep scope narrow. don’t add more agents — use the sub-agent pattern. build tools the model can actually use. measure everything. and when the next model ships, remove the outdated patterns.&lt;/p&gt;

&lt;hr&gt;

&lt;div class=&quot;read-next&quot;&gt;
  &lt;p class=&quot;read-next-heading&quot;&gt;&lt;span&gt;Read next&lt;/span&gt;&lt;/p&gt;
  &lt;ul class=&quot;read-next-list&quot;&gt;
    &lt;li&gt;
      &lt;a class=&quot;read-next-title&quot; href=&quot;/harness-tax&quot; target=&quot;_blank&quot;&gt;The Harness Tax: The Dead Weight Inside Your Coding Agent&lt;/a&gt;
      &lt;p class=&quot;read-next-desc&quot;&gt;Claude Code used 83k tokens to write a Fibonacci script. Pi used 8k. Same task. Same output. Here&apos;s where the rest of the tokens go — and what the harness is actually costing you.&lt;/p&gt;
      &lt;p class=&quot;read-next-meta&quot;&gt;Agents · Apr 13, 2026&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;a class=&quot;read-next-title&quot; href=&quot;/llm-pricing&quot; target=&quot;_blank&quot;&gt;LLM Pricing Is 100x Harder Than You Think&lt;/a&gt;
      &lt;p class=&quot;read-next-desc&quot;&gt;We&apos;ve tracked $180M in LLM spend across 3,500+ models. Here are the 6 hidden patterns that break cost attribution.&lt;/p&gt;
      &lt;p class=&quot;read-next-meta&quot;&gt;Pricing · Apr 15, 2026&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

&lt;h2 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h2&gt;

&lt;p&gt;https://x.com/sambharia/status/2043703343453987133?s=20&lt;/p&gt;

&lt;p&gt;https://x.com/Vtrivedy10/status/2031408954517971368&lt;/p&gt;

&lt;p&gt;https://www.humanlayer.dev/blog/12-factor-agents&lt;/p&gt;

&lt;style&gt;
  .post-kicker {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #3b82f6;
    margin: 0 0 0.5rem;
    font-weight: 600;
  }
  .post-lede {
    color: #525252;
    font-size: 1.05em;
    line-height: 1.55;
    margin: 0 0 1.25rem;
  }
  .read-next {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
  }
  .read-next-heading {
    display: flex;
    align-items: center;
    gap: 1rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #404040;
    margin: 0 0 1.25rem;
  }
  .read-next-heading::before,
  .read-next-heading::after {
    content: &quot;&quot;;
    flex: 1;
    height: 1px;
    background: #d4d4d4;
  }
  .read-next-heading span {
    white-space: nowrap;
  }
  .read-next-list {
    list-style: none;
    padding: 0;
    margin: 0;
  }
  .read-next-list li {
    margin-bottom: 1.75rem;
  }
  .read-next-list li:last-child {
    margin-bottom: 0;
  }
  .read-next-title {
    font-weight: 700;
    font-size: 1.05em;
    text-decoration: none;
    border-bottom: none;
    padding: 0;
  }
  .read-next-title:hover {
    text-decoration: underline;
  }
  .read-next-desc {
    margin: 0.35rem 0 0.35rem;
    font-size: 0.88em;
    line-height: 1.55;
    color: #404040;
  }
  .read-next-meta {
    margin: 0;
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #737373;
  }
&lt;/style&gt;

</content>
    <category term="ai" /><category term="agents" /><category term="llm" /><category term="history" /><category term="portkey" />
  </entry>
  
  <entry>
    <title>LLM Pricing Is 100x Harder Than You Think</title>
    <link href="https://sambharia.com/llm-pricing" rel="alternate" type="text/html" title="LLM Pricing Is 100x Harder Than You Think" />
    <id>https://sambharia.com/llm-pricing</id>
    <published>2026-04-15T00:00:00+00:00</published>
    <updated>2026-04-19T00:00:00+00:00</updated>
    <summary type="html">This article was first published on portkey.ai/blog.</summary>
    <content type="html">&lt;p&gt;&lt;em&gt;This article was first published on &lt;a href=&quot;https://portkey.ai/blog/llm-pricing-2/&quot; target=&quot;_blank&quot;&gt;portkey.ai/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p class=&quot;post-lede&quot;&gt;LLM pricing is surprisingly difficult. We&apos;ve tracked $180M in LLM spend across 3,500+ models. Here are the 6 hidden patterns that break cost attribution, the architecture we built to solve them, and we&apos;re open-sourcing all of it.&lt;/p&gt;

&lt;p&gt;Earlier this month, we open-sourced Portkey’s &lt;a href=&quot;https://github.com/portkey-ai/models&quot; target=&quot;_blank&quot;&gt;model pricing database&lt;/a&gt; — 3,500+ models across 50+ providers. The same data we use to attribute cost for enterprises processing trillions of tokens through our gateway every day.&lt;/p&gt;

&lt;p&gt;Turns out, a lot of teams needed this.&lt;/p&gt;

&lt;p&gt;The entire industry is focused on harness design, managed agents, model benchmark scores. Meanwhile, there’s no common ground on something more fundamental: &lt;strong&gt;How do you actually attribute cost to model usage?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about it. Most projects maintain an in-house pricing database. A JSON file somewhere in your repo with model names and prices. &lt;a href=&quot;https://portkey.ai/blog/the-harness-tax/&quot; target=&quot;_blank&quot;&gt;OpenCode&lt;/a&gt; has one of these. So does &lt;a href=&quot;https://openclaw.ai/&quot; target=&quot;_blank&quot;&gt;OpenClaw&lt;/a&gt;, &lt;a href=&quot;https://github.com/danny-avila/librechat&quot; target=&quot;_blank&quot;&gt;LibreChat&lt;/a&gt;, Pi, Theo’s T3 code. I keep finding new ones.&lt;/p&gt;

&lt;p&gt;The pattern is clear: everyone builds their own thing, it’s accurate for a few weeks, then it drifts. There’s no canonical source. No API you can just call. No dataset comprehensive enough to handle the weird edge cases.&lt;/p&gt;

&lt;p&gt;Three years post-ChatGPT, there’s still no standard way to calculate what a single request costs across providers.&lt;/p&gt;

&lt;p&gt;At Portkey we’ve spent three years building the infrastructure for this. Here’s everything we’ve learned, and we’re releasing the full stack so you don’t have to rebuild it yourself:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/portkey-ai/models&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Model Data + Free API&lt;/strong&gt;&lt;/a&gt;: Updated daily at &lt;a href=&quot;https://portkey.ai/models&quot; target=&quot;_blank&quot;&gt;portkey.ai/models&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/portkey-ai/gateway&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Portkey’s Gateway&lt;/strong&gt;&lt;/a&gt;: Open-source AI gateway with built-in pricing engine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-6-patterns-that-break-pricing&quot;&gt;The 6 patterns that break pricing&lt;/h2&gt;

&lt;p&gt;These aren’t edge cases. Every one of them has caused real cost discrepancies for teams using the models.&lt;/p&gt;

&lt;h3 id=&quot;1-thinking-tokens&quot;&gt;1. Thinking tokens&lt;/h3&gt;

&lt;p&gt;Reasoning models like &lt;a href=&quot;https://platform.openai.com/docs/models/o3&quot; target=&quot;_blank&quot;&gt;o3&lt;/a&gt; and &lt;a href=&quot;https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking&quot; target=&quot;_blank&quot;&gt;Claude with extended thinking&lt;/a&gt; consume tokens for internal reasoning that never appear in the response. You still get charged for them.&lt;/p&gt;

&lt;p&gt;OpenAI’s o1-preview has a 4× output-to-input price ratio ($15/M input, $60/M output). Most of that gap is reasoning overhead. If your system only counts visible output tokens, you’ll undercount agentic workloads by 30–40%.&lt;/p&gt;

&lt;h3 id=&quot;2-cache-asymmetry&quot;&gt;2. Cache asymmetry&lt;/h3&gt;

&lt;p&gt;Prompt caching economics are &lt;em&gt;different&lt;/em&gt; per provider in ways that matter.&lt;/p&gt;

&lt;p&gt;Anthropic charges 25% more for cache writes ($3.75/M vs $3.00/M regular input), with reads at $0.30/M. OpenAI charges nothing for writes. Reads get discounted. If you apply a single “cache discount” multiplier across both, your numbers are wrong for at least one of them.&lt;/p&gt;

&lt;h3 id=&quot;3-context-thresholds&quot;&gt;3. Context thresholds&lt;/h3&gt;

&lt;p&gt;OpenAI, Anthropic, and Google all have tiered pricing based on context length. Cross 128K tokens and per-token cost can double. $0.075/M becomes $0.15/M. Nothing in the API response tells you which tier you hit. The request just works. Your cost estimate is silently wrong.&lt;/p&gt;

&lt;h3 id=&quot;4-same-model-different-prices&quot;&gt;4. Same model, different prices&lt;/h3&gt;

&lt;p&gt;Kimi K2.5 costs $0.5 input / $2.8 output on Together AI, $0.6 input / $3 output on Fireworks. You can’t just track “Kimi K2.5.” You need “Kimi K2.5 &lt;em&gt;on Together AI&lt;/em&gt;.”&lt;/p&gt;

&lt;p&gt;And it gets worse: Bedrock prepends regional prefixes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;us.meta.llama&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eu.anthropic.claude-...&lt;/code&gt;) that need stripping before you can even look up the price. Azure returns deployment names instead of model identifiers. You need an extra API call to figure out what model you’re running.&lt;/p&gt;

&lt;h3 id=&quot;5-non-token-billing&quot;&gt;5. Non-token billing&lt;/h3&gt;

&lt;p&gt;DALL·E 3 bills by image quality and resolution. Video generation charges per second. Realtime audio has separate input/output rates. Embeddings are input-only. Fine-tuning is per-token on some models, per-hour on others. Each needs different fields from the request and maps to a completely different pricing structure.&lt;/p&gt;

&lt;h3 id=&quot;6-new-dimensions-keep-appearing&quot;&gt;6. New dimensions keep appearing&lt;/h3&gt;

&lt;p&gt;We started with two billing dimensions: input tokens and output tokens. Now there are over twenty. Web search has per-search pricing. Google’s Grounding with Search has its own rate structure. Tool use, code execution — each ships with its own cost model, and new ones appear faster than providers update their documentation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/llm-pricing-dimensions.png&quot; alt=&quot;LLM pricing complexity — 20+ billing dimensions across 50+ providers&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-it-matters&quot;&gt;Why it matters&lt;/h2&gt;

&lt;p&gt;Every enterprise wants to adopt AI. Making it actually work is another story. The moment you move past prototypes, cost attribution becomes a dealbreaker:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;FinOps goes blind.&lt;/strong&gt; Teams running hundreds of model variants across departments need per-team, per-user cost breakdowns. When pricing is wrong, the AI budget becomes a single line item nobody can decompose or optimize.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Margins become guesswork.&lt;/strong&gt; If you’re reselling LLM access — and increasingly everyone is — inaccurate cost data means you’re either leaking money or overcharging customers.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Budgets can’t be enforced.&lt;/strong&gt; You can set per-team spending limits, but limits only work if cost data is accurate. A model reporting $0 per request will never trip an alert.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Shipping slows down.&lt;/strong&gt; Teams get blocked on AI features because nobody can answer “what will this cost at scale?”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;how-portkeys-gateway-handles-this&quot;&gt;How Portkey’s gateway handles this&lt;/h2&gt;

&lt;p&gt;The gateway normalizes every provider response into a single cost structure. Model identifiers get resolved, usage gets normalized, and cost gets tagged per-team and per-user at the routing layer, before it hits your logs.&lt;/p&gt;

&lt;p&gt;Under the hood, the architecture separates three things that change at different rates:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Provider Response → Unified Gateway → Pricing Data → Pricing Logic → Cost
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/llm-pricing-gateway-architecture.png&quot; alt=&quot;Gateway architecture — three layers that change independently&quot;&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/llm-pricing-normalization.png&quot; alt=&quot;Provider response normalization across 50+ providers&quot;&gt;&lt;/p&gt;

&lt;p&gt;When a provider changes their response format, we update the extraction. When rates change, we update config. When new dimensions appear, we extend the schema. Each layer changes independently.&lt;/p&gt;

&lt;p&gt;The normalization is where most of the complexity lives. Every provider returns usage data differently. OpenAI gives you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt_tokens&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;completion_tokens&lt;/code&gt;. Anthropic gives you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input_tokens&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output_tokens&lt;/code&gt;. Bedrock prepends regional prefixes. Azure returns deployment names. We normalize everything into one structure:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/llm-pricing-normalized-tokens.png&quot; alt=&quot;Normalized token schema — handles 20+ billing dimensions&quot;&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;additionalUnits&lt;/code&gt; map is what lets us handle new billing dimensions (web search, grounding, tool use) without schema changes. They just become new keys.&lt;/p&gt;

&lt;p&gt;Because the gateway sits at the routing layer, it sees every request before it hits your logs. Model identifiers get resolved, usage gets normalized, cost gets tagged. That’s what makes per-team and per-user budget limits possible. Cost attribution happens at the source rather than being reconstructed after the fact from incomplete data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/llm-pricing-budget-attribution.png&quot; alt=&quot;Per-team and per-user budget attribution in Portkey&quot;&gt;&lt;/p&gt;

&lt;p&gt;It’s worth noting that Stripe recently launched their own AI Gateway specifically for LLM token billing, routing requests through a layer that meters usage per customer, per model, per token type. Same core insight: the centralized proxy is the natural place to solve cost attribution.&lt;/p&gt;

&lt;h2 id=&quot;how-we-keep-3500-models-accurate&quot;&gt;How we keep 3,500+ models accurate&lt;/h2&gt;

&lt;p&gt;Building the system was the easier part. Keeping it updated across 3,500+ models is the real challenge. Models launch weekly. Pricing changes without changelog entries. Context thresholds get buried in documentation footnotes. No human team can keep up manually.&lt;/p&gt;

&lt;p&gt;We built an agent for this using the Claude Agent SDK, with tools for fetching model lists from provider APIs, web scraping, and GitHub integration.&lt;/p&gt;

&lt;p&gt;The key design decision: &lt;strong&gt;provider-specific logic lives in skill files, not code.&lt;/strong&gt; A skill file is a markdown document describing how to handle a specific provider — where to find model lists, how to scrape pricing, what quirks to watch for. When Anthropic changes something, we update the skill file. Not the agent. Not the codebase.&lt;/p&gt;

&lt;p&gt;The agent loads skill files, fetches model lists, scrapes pricing sources, formats everything to schema, and opens PRs with citations. It costs about $2–3 per provider run. Novel pricing structures still confuse it. Humans handle judgment calls. But it covers the tedious work that was eating up our time.&lt;/p&gt;

&lt;h2 id=&quot;pricing-complexity-isnt-slowing-down&quot;&gt;Pricing complexity isn’t slowing down&lt;/h2&gt;

&lt;p&gt;New models, new billing dimensions, new provider quirks. If your cost dashboards don’t match your invoices, this is probably why.&lt;/p&gt;

&lt;p&gt;We’re releasing everything so you don’t have to rebuild it from scratch:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/portkey-ai/models&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Model Data + Free API&lt;/strong&gt;&lt;/a&gt;: Updated daily at &lt;a href=&quot;https://portkey.ai/models&quot; target=&quot;_blank&quot;&gt;portkey.ai/models&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/portkey-ai/gateway&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Portkey’s Gateway&lt;/strong&gt;&lt;/a&gt;: Open-source AI gateway with built-in pricing engine&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;style&gt;
  .post-kicker {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #3b82f6;
    margin: 0 0 0.5rem;
    font-weight: 600;
  }
  .post-lede {
    color: #525252;
    font-size: 1.05em;
    line-height: 1.55;
    margin: 0 0 1.25rem;
  }
&lt;/style&gt;

</content>
    
  </entry>
  
  <entry>
    <title>The Harness Tax: The Dead Weight Inside Your Coding Agent</title>
    <link href="https://sambharia.com/harness-tax" rel="alternate" type="text/html" title="The Harness Tax: The Dead Weight Inside Your Coding Agent" />
    <id>https://sambharia.com/harness-tax</id>
    <published>2026-04-13T00:00:00+00:00</published>
    <updated>2026-04-19T00:00:00+00:00</updated>
    <summary type="html">Agents</summary>
    <content type="html">&lt;p class=&quot;post-kicker&quot;&gt;Agents&lt;/p&gt;

&lt;p class=&quot;post-lede&quot;&gt;Claude Code used 83k tokens to write a Fibonacci script. &lt;a href=&quot;https://github.com/badlogic/pi-mono?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Pi&lt;/a&gt; (&lt;a href=&quot;https://openclaw.ai/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;OpenClaw&lt;/a&gt;) used 8k. Same task. Same output. Where did the rest of the tokens go? Here&apos;s the tax no one&apos;s talking about.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/harness-tax-10x-total-tokens.png&quot; alt=&quot;The Harness Tax — same task, same output, 10× the tokens&quot;&gt;&lt;/p&gt;

&lt;p class=&quot;post-caption&quot;&gt;Claude Code used 83k tokens to write a Fibonacci script. Pi (OpenClaw) used 8k. Same task. Same output. Where did the rest of the tokens go? Here&apos;s the tax no one&apos;s talking about.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://x.com/hwchase17/status/2042978500567609738?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Harnesses are not going away.&lt;/a&gt; Even the best models rely on them. Claude Code alone has ~512k lines of harness code. But nobody talks about what that harness actually costs you at inference time.&lt;/p&gt;

&lt;p&gt;I wanted to know: when using coding agents, how much of the payload that hits the model is actually my message? And how much is the harness overhead added?&lt;/p&gt;

&lt;p&gt;So I pointed three agents at &lt;a href=&quot;https://portkey.ai/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Portkey’s&lt;/a&gt; gateway and captured every request — &lt;a href=&quot;https://github.com/badlogic/pi-mono?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Pi&lt;/a&gt; (the harness behind &lt;a href=&quot;https://openclaw.ai/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;OpenClaw&lt;/a&gt;), &lt;a href=&quot;https://openai.com/index/codex/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;OpenAI Codex&lt;/a&gt;, and &lt;a href=&quot;https://claude.ai/code&quot; target=&quot;_blank&quot;&gt;Claude Code&lt;/a&gt;. Same request and complete token visibility. Then I gave each one the same two messages:&lt;/p&gt;

&lt;div class=&quot;callout-messages&quot;&gt;
  &lt;p&gt;Message 1: hey&lt;/p&gt;
  &lt;p&gt;Message 2: write a simple python script to check fibonacci series and save on desktop as agent.py&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Pi sent ~2,600 input tokens. Claude Code sent ~27,000. A 10× spread. Same task. Same model capability. The difference was pure harness overhead.&lt;/p&gt;

&lt;h2 id=&quot;the-harness-tax&quot;&gt;The Harness Tax&lt;/h2&gt;

&lt;div class=&quot;tip-callout&quot;&gt;
  &lt;p&gt;&lt;span class=&quot;tip-icon&quot; aria-hidden=&quot;true&quot;&gt;💡&lt;/span&gt; &lt;strong&gt;The Harness Tax&lt;/strong&gt; is every token your agent spends on itself before it spends a single token on your task.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/harness-tax-input-output-bars.png&quot; alt=&quot;The Harness Tax — same Fibonacci script, same output, wildly different token costs&quot;&gt;&lt;/p&gt;

&lt;p&gt;You pay this tax before the model does a single unit of useful work. Every agent has one. You never see it unless you look at raw request logs. I routed all three agents through a gateway to get that visibility.&lt;/p&gt;

&lt;h2 id=&quot;what-goes-into-the-harness-tax&quot;&gt;What Goes Into the Harness Tax?&lt;/h2&gt;

&lt;p&gt;Every request a coding agent makes to the model carries the full harness payload: tool definitions, system prompt, memory instructions, behavioral routing, and conversation history. All of it. On every turn.&lt;/p&gt;

&lt;p&gt;Claude Code’s harness costs roughly 27,000 input tokens per request. Codex costs about 15,000. Pi costs about 2,600.&lt;/p&gt;

&lt;p&gt;And because the conversation history includes the model’s previous responses — which were themselves inflated by verbose tool-call formatting — the payload grows faster than your actual conversation does.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/harness-tax-per-request-stacked.png&quot; alt=&quot;Per-request input tokens — Pi, OpenAI Codex, and Claude Code&quot;&gt;&lt;/p&gt;

&lt;p&gt;A real coding session runs 30 to 50 turns. At Claude Code’s rate, a 40-turn session burns through 1.12 million input tokens. Roughly half of those are harness overhead.&lt;/p&gt;

&lt;div class=&quot;tip-callout&quot;&gt;
  &lt;p&gt;&lt;span class=&quot;tip-icon&quot; aria-hidden=&quot;true&quot;&gt;💡&lt;/span&gt; &lt;strong&gt;You pay the harness tax whether you use the tools or not.&lt;/strong&gt; The 24 extra tools in Claude Code were defined but never called. Their definitions shipped on every request anyway.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;context-rot&quot;&gt;Context Rot&lt;/h2&gt;

&lt;p&gt;The harness tax isn’t just a cost problem. It’s an attention problem. Every extra token competes with your actual task: your code, your files, your intent.&lt;/p&gt;

&lt;p&gt;As the context window fills, the model gets worse at reasoning over the tokens that matter. Every token the harness adds competes for attention against your code, your files, and your actual task. On a complex refactor where the model needs to hold three source files, a test suite, and twenty turns of conversation, 28,000 tokens of framework plumbing aren’t sitting idle. They’re noise.&lt;/p&gt;

&lt;div class=&quot;tip-callout&quot;&gt;
  &lt;p&gt;&lt;span class=&quot;tip-icon&quot; aria-hidden=&quot;true&quot;&gt;💡&lt;/span&gt; &lt;strong&gt;A 200k context window carrying 28k tokens of harness overhead isn&apos;t a 200k window.&lt;/strong&gt; It&apos;s a 172k window with worse attention distribution.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;The harness rots in a second way: staleness. Every component encodes an assumption about what the model can’t do on its own. Those assumptions go stale fast. More on that below.&lt;/p&gt;

&lt;h2 id=&quot;thin-harness-fat-skills&quot;&gt;Thin Harness, Fat skills&lt;/h2&gt;

&lt;p&gt;Pi gives the model four capabilities: read, write, edit a file, and run a shell command. That’s the entire tool surface.&lt;/p&gt;

&lt;p&gt;The bet is that a model trained on millions of shell sessions, the internet, and GitHub repos already knows how to compose those primitives into anything else. You don’t need a dedicated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list_directory&lt;/code&gt; tool when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls -la&lt;/code&gt; exists. You don’t need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_files&lt;/code&gt; when the model can write &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep -r&lt;/code&gt; on its own.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“All frontier models have been RL-trained up the wazoo. They inherently understand what a coding agent is.”&lt;/em&gt;
— Mario Zechner, Pi’s creator&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://x.com/hwchase17/status/2042978500567609738?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Anthropic’s harness engineering team&lt;/a&gt; demonstrated this concretely over three model generations. Their coding agent harness for Sonnet 4.5 required context resets because the model would start wrapping up work prematurely as the window filled. Opus 4.5 shipped — resets became unnecessary. Opus 4.6 shipped — they stripped out sprint decomposition entirely, and it still worked better.&lt;/p&gt;

&lt;p&gt;Three model generations. Three layers of harness removed. Load-bearing in January, dead weight by March.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Harnesses encode assumptions that go stale as models improve — &lt;a href=&quot;https://www.anthropic.com/engineering&quot; target=&quot;_blank&quot;&gt;Anthropic Engineering&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An agent has three layers. Complexity should push &lt;strong&gt;up&lt;/strong&gt; into the model, which gets better at reasoning, planning, and self-correction with every release. It should push &lt;strong&gt;down&lt;/strong&gt; into infrastructure, where routing, governance, observability, and cost controls don’t ride along in the context window. The harness in the middle should carry as little as possible.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/harness-tax-model-harness-infra-layers.png&quot; alt=&quot;Model, harness, and infrastructure — the harness is where the tax lives&quot;&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-this-means&quot;&gt;What This Means&lt;/h2&gt;

&lt;p&gt;This was a narrow benchmark. Two messages, one trivial task. Claude Code’s deep tooling may earn back its overhead on complex work that genuinely exercises those 28 tools.&lt;/p&gt;

&lt;p&gt;What this benchmark does show: the overhead exists, it’s measurable, and almost nobody is looking at it. For most tasks, the model is carrying 15,000 tokens of framework plumbing it doesn’t need. And that overhead is growing slower than models are improving, which means the tax gets harder to justify.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;Route your agent through &lt;a href=&quot;https://portkey.ai/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Portkey&lt;/a&gt; to measure your own harness tax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/badlogic/pi-mono?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Mario Zechner’s blog post on building Pi&lt;/a&gt; — the design rationale&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openclaw.ai/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Armin Ronacher: “Pi: The Minimal Agent Within OpenClaw”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;
&lt;a href=&quot;https://github.com/badlogic/pi-mono?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;Pi on GitHub&lt;/a&gt; · &lt;a href=&quot;https://openclaw.ai/?ref=portkey.ai&quot; target=&quot;_blank&quot;&gt;OpenClaw&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was first published on &lt;a href=&quot;https://x.com/portkeyai&quot; target=&quot;_blank&quot;&gt;X&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;style&gt;
  .post-kicker {
    font-size: 0.75rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #3b82f6;
    margin: 0 0 0.5rem;
    font-weight: 600;
  }
  .post-lede {
    color: #525252;
    font-size: 1.05em;
    line-height: 1.55;
    margin: 0 0 1.25rem;
  }
  .post-caption {
    font-size: 0.85em;
    font-style: italic;
    color: #737373;
    margin: -0.5rem 0 1.5rem;
    line-height: 1.5;
  }
  .callout-messages {
    background: #faf8f5;
    border: 1px solid #e7e2d9;
    border-radius: 6px;
    padding: 1rem 1.25rem;
    margin: 1.25rem 0;
  }
  .callout-messages p {
    margin: 0 0 0.5rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.92em;
    line-height: 1.5;
  }
  .callout-messages p:last-child {
    margin-bottom: 0;
  }
  .tip-callout {
    border-left: 4px solid #93c5fd;
    background: #f8fafc;
    padding: 0.85rem 1rem 0.85rem 1rem;
    margin: 1.25rem 0;
    border-radius: 0 6px 6px 0;
  }
  .tip-callout p {
    margin: 0;
    font-size: 0.95em;
    line-height: 1.55;
  }
  .tip-icon {
    margin-right: 0.25rem;
  }
  .read-next {
    margin-top: 2.5rem;
    padding-top: 1.5rem;
  }
  .read-next-heading {
    display: flex;
    align-items: center;
    gap: 1rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #404040;
    margin: 0 0 1.25rem;
  }
  .read-next-heading::before,
  .read-next-heading::after {
    content: &quot;&quot;;
    flex: 1;
    height: 1px;
    background: #d4d4d4;
  }
  .read-next-heading span {
    white-space: nowrap;
  }
  .read-next-list {
    list-style: none;
    padding: 0;
    margin: 0;
  }
  .read-next-list li {
    margin-bottom: 1.75rem;
  }
  .read-next-list li:last-child {
    margin-bottom: 0;
  }
  .read-next-title {
    font-weight: 700;
    font-size: 1.05em;
    text-decoration: none;
    border-bottom: none;
    padding: 0;
  }
  .read-next-title:hover {
    text-decoration: underline;
  }
  .read-next-desc {
    margin: 0.35rem 0 0.35rem;
    font-size: 0.88em;
    line-height: 1.55;
    color: #404040;
  }
  .read-next-meta {
    margin: 0;
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #737373;
  }
&lt;/style&gt;

</content>
    <category term="ai" /><category term="agents" /><category term="llm" /><category term="portkey" />
  </entry>
  
</feed>
