<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maksim Danilchenko</title>
    <description>The latest articles on DEV Community by Maksim Danilchenko (@dmaxdev).</description>
    <link>https://dev.to/dmaxdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3851903%2F271b9f0d-273c-44e2-a2c7-0d4ec886b1c5.jpeg</url>
      <title>DEV Community: Maksim Danilchenko</title>
      <link>https://dev.to/dmaxdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmaxdev"/>
    <language>en</language>
    <item>
      <title>Kimi K3 Review: The 2.8T Open Model That Beats Claude on Paper</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Wed, 22 Jul 2026 08:45:26 +0000</pubDate>
      <link>https://dev.to/dmaxdev/kimi-k3-review-the-28t-open-model-that-beats-claude-on-paper-oog</link>
      <guid>https://dev.to/dmaxdev/kimi-k3-review-the-28t-open-model-that-beats-claude-on-paper-oog</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Kimi K3 is the largest open-weight model anyone has shipped: 2.8 trillion parameters, a million-token context, and a price that undercuts Claude by roughly 3x. On the leaderboards it beats Claude Fable 5 at frontend coding and lands third overall on GDPval, which is genuinely impressive for weights you can download. After a week of feeding it real Go and Python work, the picture got messier. It's slow, it invents APIs that don't exist more often than any frontier model I've used this year, and most of its headline numbers are still Moonshot's own. Worth running for cost-sensitive batch work; I would not hand it an unsupervised agent loop yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I bothered testing this one
&lt;/h2&gt;

&lt;p&gt;I review a lot of models and most open-weight releases don't survive contact with a real repo. &lt;a href="https://www.danilchenko.dev/posts/deepseek-v4-pro-review/" rel="noopener noreferrer"&gt;DeepSeek V4 Pro&lt;/a&gt; and &lt;a href="https://www.danilchenko.dev/posts/glm-5-2-review/" rel="noopener noreferrer"&gt;GLM-5.2&lt;/a&gt; were the two that did. Both were cheap, competent, and good enough that I kept them in rotation for grunt work. When Moonshot dropped Kimi K3 on July 16 and the first benchmark screenshots showed it &lt;em&gt;ahead&lt;/em&gt; of Claude Fable 5, I moved it to the top of my testing queue.&lt;/p&gt;

&lt;p&gt;I ran K3 through the API (OpenRouter forwards to Moonshot's single hosted endpoint right now) for about a week: a Go service refactor, a pandas-to-Polars migration, a batch of PR reviews, and the usual pile of "explain this stack trace" prompts. I paid for the tokens myself, so the cost math below is real. What follows is what the leaderboards don't tell you: where a 2.8T open model actually helps, and where it quietly wastes your afternoon.&lt;/p&gt;

&lt;p&gt;One caveat up front, and it's a big one: at the time of writing, the open weights aren't public yet. Moonshot says they land July 27, ten days after launch, under a modified MIT-style license. Everything in this review is based on the hosted API. If the weight release slips or the license has teeth, half the appeal evaporates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Kimi K3 actually is
&lt;/h2&gt;

&lt;p&gt;Kimi K3 is a Mixture-of-Experts model with about 2.8 trillion total parameters. Moonshot reports 896 experts with 16 active per token, which is why a model this size can serve at Sonnet-ish prices: you only pay to run a sliver of it on each forward pass. The context window is 1,048,576 tokens, the same 1M ballpark the Gemini line reaches, and it's tuned hard for long-horizon coding and agent loops rather than chat.&lt;/p&gt;

&lt;p&gt;At launch there are two variants: &lt;strong&gt;K3 Max&lt;/strong&gt; for interactive chat and agent tasks, and &lt;strong&gt;K3 Swarm Max&lt;/strong&gt; for large-scale parallel processing. Most people will only touch K3 Max. Swarm Max is aimed at the "fan out 200 requests and merge" workloads that batch pipelines and eval harnesses generate.&lt;/p&gt;

&lt;p&gt;The "largest open-weight model ever" line is doing a lot of marketing work, but it's accurate. Nothing else with downloadable weights comes close to 2.8T. Whether you can &lt;em&gt;run&lt;/em&gt; it locally is a separate question I'll get to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting it running
&lt;/h2&gt;

&lt;p&gt;The API is a plain OpenAI-compatible endpoint, so if you already have code hitting GPT or Claude, swapping in K3 is a two-line change. Here's the exact call I used to smoke-test it:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://openrouter.ai/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENROUTER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moonshotai/kimi-k3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a senior Go engineer. Be terse.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a worker pool in Go 1.26 that &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processes a channel of jobs with N workers &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and returns the first error via errgroup.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output on my run (trimmed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "context"
    "golang.org/x/sync/errgroup"
)

func Process(ctx context.Context, jobs &amp;lt;-chan Job, n int) error {
    g, ctx := errgroup.WithContext(ctx)
    for i := 0; i &amp;lt; n; i++ {
        g.Go(func() error {
            for {
                select {
                case &amp;lt;-ctx.Done():
                    return ctx.Err()
                case job, ok := &amp;lt;-jobs:
                    if !ok {
                        return nil
                    }
                    if err := job.Do(ctx); err != nil {
                        return err
                    }
                }
            }
        })
    }
    return g.Wait()
}

tokens: 512
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's correct, idiomatic, and closes over the loop variable safely (Go 1.22+ semantics). No complaints on the code itself. The complaint is what the timer showed: that response took noticeably longer than the same prompt to Fable 5 or GPT-5.6. Which brings me to the thing the leaderboards bury.&lt;/p&gt;

&lt;h2&gt;
  
  
  The speed problem is real
&lt;/h2&gt;

&lt;p&gt;Every hands-on account I've seen, including mine, lands on the same finding: K3 is slow. &lt;a href="https://thenewstack.io/kimi-k3-fable-coding-benchmark/" rel="noopener noreferrer"&gt;The New Stack ran a coding comparison&lt;/a&gt; and clocked it at roughly &lt;strong&gt;4x slower&lt;/strong&gt; than Claude Fable 5 on equivalent tasks. My informal timings weren't that clean, but "meaningfully slower" is not in dispute.&lt;/p&gt;

&lt;p&gt;For an interactive agent loop, 4x slower is disqualifying. When I'm iterating with a coding agent, latency is the whole experience. A tool call that takes six seconds instead of two turns a ten-minute session into forty. I stopped using K3 for anything conversational within a day.&lt;/p&gt;

&lt;p&gt;Where the slowness stops mattering is batch work. If you're running a nightly job that summarizes 5,000 support tickets or generates first-draft docstrings across a monorepo, nobody's watching the clock. There, K3's price does the talking. That split defines the whole model: great for offline, painful for interactive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmarks, with the asterisks attached
&lt;/h2&gt;

&lt;p&gt;K3 earned its headlines on the benchmarks. &lt;a href="https://venturebeat.com/technology/chinas-moonshot-ai-releases-kimi-k3-the-largest-open-source-model-ever-rivaling-top-u-s-systems" rel="noopener noreferrer"&gt;As VentureBeat's launch coverage laid out&lt;/a&gt;, these are the numbers being quoted, and I'm labeling every one of them as &lt;strong&gt;reported&lt;/strong&gt;, because most trace back to Moonshot's own eval runs or single-leaderboard placements rather than broad independent replication.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Kimi K3 (reported)&lt;/th&gt;
&lt;th&gt;For reference&lt;/th&gt;
&lt;th&gt;Placement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend Code Arena&lt;/td&gt;
&lt;td&gt;1,679&lt;/td&gt;
&lt;td&gt;~48 pts ahead of Claude Fable 5&lt;/td&gt;
&lt;td&gt;1st&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDPval-AA v2&lt;/td&gt;
&lt;td&gt;~1,668&lt;/td&gt;
&lt;td&gt;Fable 5 Max ~1,760 · GPT-5.6 Sol Max ~1,748 · Opus 4.8 1,600&lt;/td&gt;
&lt;td&gt;3rd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AA-Briefcase&lt;/td&gt;
&lt;td&gt;~1,547&lt;/td&gt;
&lt;td&gt;Fable 5 Max ~1,583 · GPT-5.6 Sol Max 1,495&lt;/td&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench Verified&lt;/td&gt;
&lt;td&gt;~65–67%&lt;/td&gt;
&lt;td&gt;Same tier as top OpenAI/Anthropic models&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things stand out. First, beating Claude Fable 5 on the &lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/moonshot-releases-2-8-trillion-parameter-kimi-k3" rel="noopener noreferrer"&gt;Frontend Code Arena&lt;/a&gt; is a real result. Arena runs are human-preference votes on built UIs, and that one leaderboard is independent of Moonshot. Second, on GDPval it lands &lt;em&gt;behind&lt;/em&gt; the top closed models but ahead of Opus 4.8, which is the fair "third place, and that's still very good for open weights" framing.&lt;/p&gt;

&lt;p&gt;The SWE-bench figure needs the most salt. "65–67% in scaffold-assisted settings" means with a coding harness doing retries and test feedback around it. Bare-model SWE-bench numbers are always lower, and Moonshot hasn't published a clean methodology at the time of writing. Treat it as "strong tier," not a precise ranking against a specific competitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kimi K3 vs Claude vs GPT: the head-to-head
&lt;/h2&gt;

&lt;p&gt;Most people searching for K3 are really asking one question: should I use this instead of Claude or GPT? Here's the head-to-head that reflects a week of use plus the public data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Kimi K3&lt;/th&gt;
&lt;th&gt;Claude Fable 5&lt;/th&gt;
&lt;th&gt;GPT-5.6&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Weights&lt;/td&gt;
&lt;td&gt;Open (July 27)&lt;/td&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;td&gt;Closed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;~400K (est.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input price / 1M&lt;/td&gt;
&lt;td&gt;$3 ($0.30 cache-hit)&lt;/td&gt;
&lt;td&gt;~$10&lt;/td&gt;
&lt;td&gt;Premium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output price / 1M&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;~$50&lt;/td&gt;
&lt;td&gt;Premium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic reliability&lt;/td&gt;
&lt;td&gt;Uneven&lt;/td&gt;
&lt;td&gt;Best in class&lt;/td&gt;
&lt;td&gt;Very good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Cheap batch, self-hosting later&lt;/td&gt;
&lt;td&gt;Interactive coding, agents&lt;/td&gt;
&lt;td&gt;General reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;K3 and Fable 5 prices are published/reported rates; GPT-5.6 figures are approximate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On raw coding quality for a single well-scoped task, K3 is close enough to Fable 5 that you would not reliably tell them apart in a blind read. The New Stack's summary of "same results, one-third the cost, four times slower" matches my experience almost exactly: Fable 5 runs about $10 in and $50 out per million tokens, so K3's $3/$15 really is the cheaper column. If your bottleneck is API spend and not wall-clock time, that trade is a bargain.&lt;/p&gt;

&lt;p&gt;The gap opens up in agent loops. Fable 5 and GPT-5.6 recover from their own mistakes; they read a failing test, form a theory, and fix it. K3 is more likely to double down on a wrong assumption, which is a known failure mode I dug into in the piece on &lt;a href="https://www.danilchenko.dev/posts/2026-04-01-error-cascades-multi-agent-llm-systems/" rel="noopener noreferrer"&gt;multi-agent error cascades&lt;/a&gt;. If you want the current best-in-class agent, the &lt;a href="https://www.danilchenko.dev/posts/claude-fable-5-review/" rel="noopener noreferrer"&gt;Claude Fable 5 review&lt;/a&gt; still holds. K3 wins on price; Fable 5 wins on recovering from its own mistakes. Anyone doing head-to-heads through the Anthropic API will feel that difference within a session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it falls apart: hallucination
&lt;/h2&gt;

&lt;p&gt;This is the part that kept K3 out of my daily rotation. &lt;a href="https://memeburn.com/kimi-k3-beat-claude-and-gpt-at-coding-why-cant-you-fully-trust-it/" rel="noopener noreferrer"&gt;Independent testing has put its hallucination rate around &lt;strong&gt;51%&lt;/strong&gt;&lt;/a&gt;, up from a reported ~39% on its predecessor. I don't have a way to reproduce that exact figure, but the qualitative pattern showed up fast.&lt;/p&gt;

&lt;p&gt;Twice in a week, K3 confidently used APIs that don't exist. In one pandas migration it wrote &lt;code&gt;pd.read_parquet(path, engine="polars")&lt;/code&gt; as though pandas ships a Polars backend — it doesn't. In a Go task it referenced a stdlib helper that isn't in the standard library at all. Both times the surrouding code was clean and plausible, which is exactly what makes it dangerous: the wrong line reads as confidently as the right ones. A less careful reviewer merges it.&lt;/p&gt;

&lt;p&gt;For a human-in-the-loop workflow where you read every diff, this is an annoyance you can catch. For an autonomous agent that writes, runs, and commits without a person checking each step, a coin-flip hallucination rate is a hard stop. The benchmark wins and this number live in tension, and you can't take one seriously without the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing and how to run it
&lt;/h2&gt;

&lt;p&gt;Through the API, K3 &lt;a href="https://openrouter.ai/moonshotai/kimi-k3" rel="noopener noreferrer"&gt;costs $3 per million input tokens&lt;/a&gt; on a cache miss, $0.30 per million on a cache hit, and $15 per million output tokens. That's Sonnet-class pricing for a model posting near-frontier coding scores, which is the entire pitch.&lt;/p&gt;

&lt;p&gt;Local is the harder story. The weights drop July 27, but 2.8T parameters is not something you run on a workstation. Even at MoE's reduced active-parameter cost, you need serious multi-GPU hardware or a quantized community build to load it at all. "Kimi K3 on Ollama" is one of the top autocompletes for the model, and the real answer for now is: not on consumer hardware, and not on day one. Watch the Hugging Face repo and the quantization community; a usable GGUF will show up eventually, but it won't be quick. If you want a model you can actually run on a Mac today, &lt;a href="https://www.danilchenko.dev/posts/apfel-review-free-local-ai-mac/" rel="noopener noreferrer"&gt;Apfel&lt;/a&gt; is a far more realistic pick. Different weight class, but it fits in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should use it, who should skip
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Kimi K3 if:&lt;/strong&gt; you run large offline batch jobs where cost dominates and latency doesn't, you want to hedge against closed-model lock-in, or you plan to self-host once the weights and a viable serving stack exist. The price-per-quality on non-interactive work is the best I've measured this quarter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip it if:&lt;/strong&gt; you want a snappy interactive coding assistant (Fable 5 or GPT-5.6, no contest), you're building an unsupervised agent where a hallucination becomes a committed bug, or you need a model you can run locally this week. For most solo developers writing code interactively, the slowness alone rules it out.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Is Kimi K3 open source?
&lt;/h3&gt;

&lt;p&gt;The weights are open; the training data and full pipeline are not. Moonshot says the weights ship July 27, 2026 under a modified MIT-style license (the same approach as Kimi K2.6), which makes K3 open-weight rather than fully open-source. You can download and run the model, but you can't fully reproduce it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Kimi K3 better than Claude?
&lt;/h3&gt;

&lt;p&gt;On some coding leaderboards, yes: it reportedly tops the Frontend Code Arena ahead of Claude Fable 5. In day-to-day use it's competitive on single-task code quality but slower and more prone to hallucination, so Claude remains the stronger pick for interactive and agentic work.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does Kimi K3 cost?
&lt;/h3&gt;

&lt;p&gt;API pricing is $3 per million input tokens ($0.30 on cache hits) and $15 per million output tokens, roughly a third of what comparable closed models charge for similar coding quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you run Kimi K3 on Ollama or locally?
&lt;/h3&gt;

&lt;p&gt;Not on consumer hardware. At 2.8T total parameters it needs substantial multi-GPU infrastructure even as a Mixture-of-Experts model. Quantized community builds may make smaller-scale local runs possible after the July 27 weight release, but there's nothing practical for a laptop at launch.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many parameters does Kimi K3 have?
&lt;/h3&gt;

&lt;p&gt;About 2.8 trillion total parameters in a Mixture-of-Experts design, with roughly 896 experts and 16 active per token, so only a small fraction runs on any given forward pass, which is what keeps inference costs down.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://openrouter.ai/moonshotai/kimi-k3" rel="noopener noreferrer"&gt;Kimi K3 on OpenRouter&lt;/a&gt; — official API pricing and hosting details&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://venturebeat.com/technology/chinas-moonshot-ai-releases-kimi-k3-the-largest-open-source-model-ever-rivaling-top-u-s-systems" rel="noopener noreferrer"&gt;China's Moonshot AI releases Kimi K3, the largest open-source model ever — VentureBeat&lt;/a&gt; — launch coverage, specs, and benchmark placements&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/moonshot-releases-2-8-trillion-parameter-kimi-k3" rel="noopener noreferrer"&gt;Moonshot releases 2.8-trillion-parameter Kimi K3 — Tom's Hardware&lt;/a&gt; — architecture and Frontend Code Arena result&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://thenewstack.io/kimi-k3-fable-coding-benchmark/" rel="noopener noreferrer"&gt;Claude Fable 5 vs Kimi K3: same results, one-third the cost, 4x slower — The New Stack&lt;/a&gt; — independent coding comparison, latency, and pricing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.datacamp.com/blog/kimi-k3" rel="noopener noreferrer"&gt;Kimi K3 — DataCamp&lt;/a&gt; — model overview, variants, and open-weights timeline&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://memeburn.com/kimi-k3-beat-claude-and-gpt-at-coding-why-cant-you-fully-trust-it/" rel="noopener noreferrer"&gt;Kimi K3 beat Claude and GPT at coding — why can't you fully trust it? — Memeburn&lt;/a&gt; — reported hallucination-rate testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Kimi K3 is the most impressive open-weight release of the year and, for a specific job, a genuine bargain. If your workload is offline, cost-bound, and human-reviewed, download it the moment the weights land and point your batch jobs at it. But the leaderboard story and the daily-driver story are two different reviews. On the benchmarks it beats Claude; at my keyboard it was slow and it lied to me twice in a week. I'll keep it for batch grunt work and reach for Fable 5 the moment a task actually needs to be right.&lt;/p&gt;

</description>
      <category>kimik3</category>
      <category>moonshotai</category>
      <category>openweightmodels</category>
      <category>aicoding</category>
    </item>
    <item>
      <title>Go's JSON v2 (encoding/json/v2): Faster, Stricter, and What Breaks</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Sun, 19 Jul 2026 08:41:43 +0000</pubDate>
      <link>https://dev.to/dmaxdev/gos-json-v2-encodingjsonv2-faster-stricter-and-what-breaks-f4f</link>
      <guid>https://dev.to/dmaxdev/gos-json-v2-encodingjsonv2-faster-stricter-and-what-breaks-f4f</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Go's new &lt;code&gt;encoding/json/v2&lt;/code&gt; is the first real rewrite of the standard library's JSON handling in fifteen years. It's a reported 2–10x faster at unmarshaling, cleans up a pile of long-standing footguns, and moves configuration from magic struct tags into explicit function options. The catch: it changes several v1 defaults you may be quietly relying on — nil slices now encode as &lt;code&gt;[]&lt;/code&gt; instead of &lt;code&gt;null&lt;/code&gt;, duplicate object keys are rejected, and field matching is case-sensitive. As of Go 1.26 it lives behind &lt;code&gt;GOEXPERIMENT=jsonv2&lt;/code&gt;, with the plan to make it the default in Go 1.27. This is a practical walkthrough of turning it on, the changes that will bite you, and how to migrate without breaking your API contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a second JSON package at all
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;encoding/json&lt;/code&gt; shipped with Go 1.0 in 2012 and barely changed since. That stability paid off for a long time, then slowly turned into a tax. The v1 package carried a set of decisions that looked reasonable in 2011 and aged into daily annoyances: it silently accepts duplicate object keys, replaces invalid UTF-8 with the Unicode replacement character instead of erroring, and marshals a &lt;code&gt;nil&lt;/code&gt; slice as &lt;code&gt;null&lt;/code&gt; rather than an empty array. Each of those has bitten real production code, including mine.&lt;/p&gt;

&lt;p&gt;The one that cost me an afternoon was the nil-slice-as-&lt;code&gt;null&lt;/code&gt; behavior. I had a Go service returning a list of items to a TypeScript frontend. When the list was empty, the backend sometimes sent &lt;code&gt;"items": null&lt;/code&gt; and sometimes &lt;code&gt;"items": []&lt;/code&gt;, depending on whether a slice had been initialized or left nil somewhere upstream. The frontend called &lt;code&gt;.map()&lt;/code&gt; on the response and blew up only on the &lt;code&gt;null&lt;/code&gt; path. It was an intermittent, hard-to-reproduce crash that came down to a Go zero-value quirk. I fixed it back then with a &lt;code&gt;make([]Item, 0)&lt;/code&gt; and a mental note. v2 fixes it at the source.&lt;/p&gt;

&lt;p&gt;That is the spirit of the whole package. The &lt;a href="https://go.dev/blog/jsonv2-exp" rel="noopener noreferrer"&gt;official experimental announcement&lt;/a&gt; frames it as fixing "15 years of accumulated sharp edges" while keeping the API recognizable. If you've written Go, &lt;code&gt;json.Marshal&lt;/code&gt; and &lt;code&gt;json.Unmarshal&lt;/code&gt; still work the way you expect. They just have saner defaults and a much faster engine underneath.&lt;/p&gt;

&lt;p&gt;If you're tracking where the language is heading more broadly, this fits the same evolutionary arc as &lt;a href="https://www.danilchenko.dev/posts/go-1-26-features/" rel="noopener noreferrer"&gt;Go 1.26's other standard-library changes&lt;/a&gt;: fewer surprises, more explicit control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually in the box
&lt;/h2&gt;

&lt;p&gt;json/v2 is not one package but two, split along a clean line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;encoding/json/jsontext&lt;/code&gt;&lt;/strong&gt; handles &lt;em&gt;syntax&lt;/em&gt;: the raw grammar of JSON. It gives you a streaming &lt;code&gt;Encoder&lt;/code&gt; and &lt;code&gt;Decoder&lt;/code&gt;, plus &lt;code&gt;Value&lt;/code&gt; and &lt;code&gt;Token&lt;/code&gt; types, and it doesn't touch reflection at all. Think of it as the tokenizer layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;encoding/json/v2&lt;/code&gt;&lt;/strong&gt; handles &lt;em&gt;semantics&lt;/em&gt;: mapping Go values to and from JSON. This is where &lt;code&gt;Marshal&lt;/code&gt;, &lt;code&gt;Unmarshal&lt;/code&gt;, and the struct-tag machinery live.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation pays off in performance-sensitive code, because you can drop down to &lt;code&gt;jsontext&lt;/code&gt; and stream tokens without paying for reflection when you don't need it. For everyday use you'll import v2 and never think about the split.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning it on
&lt;/h2&gt;

&lt;p&gt;On Go 1.25 or 1.26, the packages are hidden unless you build with the experiment flag. There's no &lt;code&gt;go get&lt;/code&gt; and no third-party module. It's already in your toolchain, just gated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# One-off run&lt;/span&gt;
&lt;span class="nv"&gt;GOEXPERIMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;jsonv2 go run main.go

&lt;span class="c"&gt;# Persist it for a session&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOEXPERIMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;jsonv2
go build ./...
go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the flag is set, the import paths become visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main.go — run with: GOEXPERIMENT=jsonv2 go run main.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="s"&gt;"encoding/json/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;      &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;   &lt;span class="s"&gt;`json:"name"`&lt;/span&gt;
    &lt;span class="n"&gt;Roles&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"roles"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c"&gt;// Roles left nil on purpose&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it and you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Ada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="p"&gt;:[]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under v1, that same struct marshals to &lt;code&gt;"roles":null&lt;/code&gt;. That single difference is the one most likely to change bytes on the wire in your service, so it's worth internalizing before anything else.&lt;/p&gt;

&lt;p&gt;If you can't move off an older Go toolchain yet, the same code is available today as a standalone module, &lt;code&gt;github.com/go-json-experiment/json&lt;/code&gt;, which is the upstream where v2 was developed. The import path differs but the API is the same, so it's a fair way to experiment on Go 1.23 or 1.24 before your production toolchain catches up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The breaking changes, ranked by how likely they are to bite
&lt;/h2&gt;

&lt;p&gt;Here's the full set of default changes, most disruptive first. Every one of them is controllable. v2 keeps the old behavior available as an option; it's just no longer the default.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;th&gt;v1 default&lt;/th&gt;
&lt;th&gt;v2 default&lt;/th&gt;
&lt;th&gt;How to restore v1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nil slice&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;json.FormatNilSliceAsNull(true)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nil map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;json.FormatNilMapAsNull(true)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field matching&lt;/td&gt;
&lt;td&gt;case-insensitive&lt;/td&gt;
&lt;td&gt;case-sensitive&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;json.MatchCaseInsensitiveNames(true)&lt;/code&gt; or &lt;code&gt;case:ignore&lt;/code&gt; tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate object keys&lt;/td&gt;
&lt;td&gt;accepted&lt;/td&gt;
&lt;td&gt;rejected&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jsontext.AllowDuplicateNames(true)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid UTF-8&lt;/td&gt;
&lt;td&gt;replaced silently&lt;/td&gt;
&lt;td&gt;rejected&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jsontext.AllowInvalidUTF8(true)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;time.Duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;encoded as int64 nanoseconds&lt;/td&gt;
&lt;td&gt;requires explicit &lt;code&gt;format&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;json:",format:nano"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Case sensitivity is the sneaky one
&lt;/h3&gt;

&lt;p&gt;The nil-slice change is loud. You'll see it in the first test that checks a response body. Case sensitivity is quiet, because it fails by &lt;em&gt;silently not matching&lt;/em&gt; rather than by erroring. Consider input from an API that uses &lt;code&gt;SCREAMING_CASE&lt;/code&gt; or &lt;code&gt;PascalCase&lt;/code&gt; keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"NAME":"Ada"}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="c"&gt;// Name is tagged json:"name"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Name=%q&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;In v1 that would have populated &lt;code&gt;u.Name&lt;/code&gt; with &lt;code&gt;"Ada"&lt;/code&gt; because matching ignored case. In v2 the field stays at its zero value and no error is raised. If you consume upstream JSON whose casing doesn't exactly match your tags, opt back in per-field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"name,case:ignore"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or globally at the call site with &lt;code&gt;json.MatchCaseInsensitiveNames(true)&lt;/code&gt;. I'd reach for the per-field tag, since it documents which fields have loose upstream contracts instead of loosening the whole struct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate keys now error
&lt;/h3&gt;

&lt;p&gt;v1 happily unmarshaled &lt;code&gt;{"id":1,"id":2}&lt;/code&gt; and kept the last value. That's a genuine security hole: different parsers picking different values for the same key is the root of a whole class of request-smuggling and access-control bugs. v2 rejects it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"id":1,"id":2}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error message is descriptive (something along the lines of a duplicate object member name for &lt;code&gt;"id"&lt;/code&gt;; the exact wording may shift while the package is experimental). If you have a legitimate reason to accept duplicates, passing &lt;code&gt;jsontext.AllowDuplicateNames(true)&lt;/code&gt; restores the old leniency (that option and &lt;code&gt;AllowInvalidUTF8&lt;/code&gt; live in the &lt;code&gt;jsontext&lt;/code&gt; package, since they're syntactic, but you pass them straight to &lt;code&gt;json.Unmarshal&lt;/code&gt; like any other option). Think hard before you reach for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options replace tag soup
&lt;/h2&gt;

&lt;p&gt;The change I like most has nothing to do with speed. In v1, everything was a struct tag or a global, and anything you couldn't express as a tag you couldn't express at all. v2 makes options first-class function arguments that you pass to &lt;code&gt;Marshal&lt;/code&gt; and &lt;code&gt;Unmarshal&lt;/code&gt; directly. The &lt;a href="https://pkg.go.dev/encoding/json/v2" rel="noopener noreferrer"&gt;package reference&lt;/a&gt; lists the full set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FormatNilSliceAsNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// keep v1 null-for-nil&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deterministic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c"&gt;// stable map key ordering&lt;/span&gt;
    &lt;span class="n"&gt;jsontext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithIndent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  "&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c"&gt;// pretty-print&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;Deterministic(true)&lt;/code&gt; option is a small quality-of-life win worth calling out: it guarantees identical inputs produce identical output bytes, which makes golden-file tests and content hashing reliable without you sorting maps by hand. You can bundle options once and reuse them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;prettyV1&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JoinOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FormatNilSliceAsNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FormatNilMapAsNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;jsontext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithIndent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  "&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prettyV1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The struct-tag vocabulary grew too. The most useful new tag is &lt;code&gt;omitzero&lt;/code&gt;, which finally fixes the ancient confusion around &lt;code&gt;omitempty&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"name"`&lt;/span&gt;
    &lt;span class="n"&gt;Start&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"start,omitzero"`&lt;/span&gt;  &lt;span class="c"&gt;// dropped when the zero Time&lt;/span&gt;
    &lt;span class="n"&gt;Tags&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"tags,omitempty"`&lt;/span&gt;  &lt;span class="c"&gt;// dropped when empty JSON ([], "", null)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;omitempty&lt;/code&gt; in v1 was notorious for not omitting a zero &lt;code&gt;time.Time&lt;/code&gt; (because a struct is never "empty" by its rule). &lt;code&gt;omitzero&lt;/code&gt; checks the Go zero value (or an &lt;code&gt;IsZero()&lt;/code&gt; method if your type has one), which is almost always what people meant in the first place. If you've ever shipped &lt;code&gt;"start":"0001-01-01T00:00:00Z"&lt;/code&gt; into a JSON response by accident, this tag is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming and the O(n²) trap
&lt;/h2&gt;

&lt;p&gt;The headline performance number is unmarshal speed, but the more interesting fix is structural. In v1, if a custom type implemented &lt;code&gt;MarshalJSON&lt;/code&gt;, encoding a large slice of that type could degrade to O(n²): each element's &lt;code&gt;MarshalJSON&lt;/code&gt; allocated a fresh &lt;code&gt;[]byte&lt;/code&gt;, which then got re-parsed and re-copied into the parent buffer. Nest that a couple of levels deep and throughput falls off a cliff.&lt;/p&gt;

&lt;p&gt;v2 adds streaming custom-marshaler interfaces that write directly into the shared encoder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Old, allocation-heavy:&lt;/span&gt;
&lt;span class="c"&gt;//   func (t T) MarshalJSON() ([]byte, error)&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;// New, streams into the parent encoder:&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;MarshalJSONTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;jsontext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jsontext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather not add methods to a type, v2 also takes function-based marshalers registered per type through &lt;code&gt;json.WithMarshalers&lt;/code&gt;. Those helpers lean on &lt;a href="https://www.danilchenko.dev/posts/go-generic-methods/" rel="noopener noreferrer"&gt;Go generics&lt;/a&gt; so you get a type-safe &lt;code&gt;func(T)&lt;/code&gt; handler instead of the old &lt;code&gt;interface{}&lt;/code&gt; juggling. For bulk output you can drive the encoder yourself and avoid building a giant in-memory value at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="s"&gt;"encoding/json/v2"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json/jsontext"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;writeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jsontext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarshalEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decode side mirrors it with &lt;code&gt;json.UnmarshalDecode&lt;/code&gt; over a &lt;code&gt;jsontext.Decoder&lt;/code&gt;, which pairs naturally with a range loop the way &lt;a href="https://www.danilchenko.dev/posts/go-iterators/" rel="noopener noreferrer"&gt;Go's iterators&lt;/a&gt; do: read one value, process it, discard it, without holding the whole document in memory. The Go team reported that Kubernetes' kube-openapi saw "orders of magnitude" improvement switching a hot path to the streaming interface. That's the kind of gain you only get from fixing the algorithmic shape of the problem, and no amount of parser tuning would have matched it.&lt;/p&gt;

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

&lt;p&gt;I want to be precise here: I did not run these numbers on my own hardware, because the machine I wrote this on is still on Go 1.24 and the package needs 1.25+. The 2–10x figure comes from the &lt;a href="https://go.dev/blog/jsonv2-exp" rel="noopener noreferrer"&gt;Go team's own benchmarks&lt;/a&gt; and third-party measurements, and I'm labeling it as reported rather than measured. Marshal is roughly at parity with v1; the big wins are on the unmarshal path, where the new parser does far less work.&lt;/p&gt;

&lt;p&gt;Don't take anyone's benchmark on faith, including mine. Here's a harness you can drop into your own repo and run against your actual payloads, which is the only benchmark worth trusting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="s"&gt;"encoding/json/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;BenchmarkUnmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"testdata/payload.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// your real data&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReportAllocs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEXPERIMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;jsonv2 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Unmarshal &lt;span class="nt"&gt;-benchmem&lt;/span&gt; ./bench/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same benchmark once with the flag and once without (swapping the import back to &lt;code&gt;encoding/json&lt;/code&gt;), and compare with &lt;code&gt;benchstat&lt;/code&gt;. Your mileage depends heavily on the shape of your data. Documents with lots of small objects and string-heavy payloads tend to show the largest gap, while a handful of big numeric arrays will show less.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safe migration plan
&lt;/h2&gt;

&lt;p&gt;The good news is that v2 is designed for gradual adoption. v1 is being reimplemented on top of v2 internally, and the two are meant to coexist for years. The Go team has said v1 won't be deprecated for years after v2 stabilizes, so you don't have to flip everything at once. Here's the order I'd follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Turn on the flag in CI first, not prod.&lt;/strong&gt; Build and run your full test suite with &lt;code&gt;GOEXPERIMENT=jsonv2&lt;/code&gt;. Most failures will be golden-file or response-body assertions catching the nil-slice and case-sensitivity changes. That's your blast-radius report, for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix contracts, not symptoms.&lt;/strong&gt; Where a test breaks because a nil slice now encodes as &lt;code&gt;[]&lt;/code&gt;, decide which representation your API actually promises. Usually &lt;code&gt;[]&lt;/code&gt; is correct and the test was wrong; occasionally a client depends on &lt;code&gt;null&lt;/code&gt; and you set &lt;code&gt;FormatNilSliceAsNull(true)&lt;/code&gt; at that boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;case:ignore&lt;/code&gt; where upstream casing is loose.&lt;/strong&gt; Audit every place you unmarshal third-party JSON and tag the fields whose casing you don't control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adopt &lt;code&gt;omitzero&lt;/code&gt;&lt;/strong&gt; in place of the &lt;code&gt;omitempty&lt;/code&gt;-on-a-struct workarounds you've accumulated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leave the streaming interfaces for later.&lt;/strong&gt; They're an optimization you can defer safely. Convert the one or two hot paths that profiling flags, and leave the rest on plain &lt;code&gt;Marshal&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you build data-heavy services, this is the same performance-versus-safety tradeoff conversation that plays out in Python land with &lt;a href="https://www.danilchenko.dev/posts/msgspec-vs-pydantic/" rel="noopener noreferrer"&gt;msgspec versus Pydantic&lt;/a&gt;: a stricter, faster serializer that asks you to be explicit about your data's shape. Go just happens to be baking that upgrade into the standard library instead of a third-party package.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Is Go json v2 stable and production-ready?
&lt;/h3&gt;

&lt;p&gt;Not yet, officially. As of Go 1.26 it's experimental, gated behind &lt;code&gt;GOEXPERIMENT=jsonv2&lt;/code&gt;, and the API can still change. The plan is to graduate it to the default in Go 1.27. Plenty of teams are already testing it in CI, but I wouldn't gate a production release on experimental behavior that might shift.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between encoding/json and encoding/json/v2?
&lt;/h3&gt;

&lt;p&gt;Same core API (&lt;code&gt;Marshal&lt;/code&gt;/&lt;code&gt;Unmarshal&lt;/code&gt;), but v2 has stricter, safer defaults (rejects duplicate keys and invalid UTF-8, matches field names case-sensitively), encodes nil slices/maps as &lt;code&gt;[]&lt;/code&gt;/&lt;code&gt;{}&lt;/code&gt; instead of &lt;code&gt;null&lt;/code&gt;, adds first-class option arguments, introduces the &lt;code&gt;omitzero&lt;/code&gt; tag, and ships a much faster unmarshaler.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much faster is Go json v2?
&lt;/h3&gt;

&lt;p&gt;Reported benchmarks put unmarshaling at roughly 2–10x faster than v1, driven by a more efficient parser. Marshaling is about the same speed as v1. The gains depend on your data shape, so benchmark your own payloads rather than trusting a single headline number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to rewrite my structs to use json v2?
&lt;/h3&gt;

&lt;p&gt;No. Existing struct tags keep working. You only touch code where a changed default affects your output: restoring &lt;code&gt;null&lt;/code&gt; for nil slices at a specific boundary, adding &lt;code&gt;case:ignore&lt;/code&gt; for loosely-cased upstreams, or swapping &lt;code&gt;omitempty&lt;/code&gt; for &lt;code&gt;omitzero&lt;/code&gt;. Everything else is a recompile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use encoding/json/v2 on older Go versions?
&lt;/h3&gt;

&lt;p&gt;The standard-library path needs Go 1.25 or newer with the experiment flag. If you're stuck on Go 1.23 or 1.24, the identical implementation is available as the standalone module &lt;code&gt;github.com/go-json-experiment/json&lt;/code&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://go.dev/blog/jsonv2-exp" rel="noopener noreferrer"&gt;A new experimental Go API for JSON — The Go Blog&lt;/a&gt; — the official announcement, rationale, and behavior changes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/encoding/json/v2" rel="noopener noreferrer"&gt;encoding/json/v2 — Go Packages&lt;/a&gt; — full API reference for functions, options, and struct tags&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pkg.go.dev/encoding/json/jsontext" rel="noopener noreferrer"&gt;encoding/json/jsontext — Go Packages&lt;/a&gt; — the syntactic streaming layer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/go-json-experiment/json" rel="noopener noreferrer"&gt;go-json-experiment/json — GitHub&lt;/a&gt; — the upstream module usable on older Go toolchains&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.dev/doc/go1.26" rel="noopener noreferrer"&gt;Go 1.26 Release Notes&lt;/a&gt; — current stable release context&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;json/v2 is the rare standard-library change that's both faster and safer, and the migration cost is mostly "run your tests and read the diffs." The performance is a nice bonus; the real value is that the defaults finally match what most Go developers meant all along: no more &lt;code&gt;null&lt;/code&gt; slices leaking into frontends, no more silently accepted duplicate keys, and case-insensitive matching stops quietly papering over schema mismatches. Turn on &lt;code&gt;GOEXPERIMENT=jsonv2&lt;/code&gt; in CI this week, see what breaks, and you'll be ready the day Go 1.27 makes it the default.&lt;/p&gt;

</description>
      <category>go</category>
      <category>json</category>
      <category>encodingjson</category>
    </item>
    <item>
      <title>How to Build an AI Agent in Go (From Scratch, No Framework)</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Mon, 13 Jul 2026 08:45:53 +0000</pubDate>
      <link>https://dev.to/dmaxdev/how-to-build-an-ai-agent-in-go-from-scratch-no-framework-5f60</link>
      <guid>https://dev.to/dmaxdev/how-to-build-an-ai-agent-in-go-from-scratch-no-framework-5f60</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;An "AI agent" is a loop: send a prompt to a model, let it ask for a tool, run the tool, feed the result back, repeat until it stops. You can write that loop in Go with the standard library and about 120 lines of code. No LangChain, no SDK, no &lt;code&gt;google.golang.org/adk&lt;/code&gt;. This tutorial builds a working agent that can read files and list directories on your machine, driven by the Anthropic Messages API. I'll show the full source, the exact JSON the API expects, the terminal output from a real run, and the two mistakes that cost me an hour the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why write this in Go at all?
&lt;/h2&gt;

&lt;p&gt;Python owns the agent tutorials, and for good reason — every framework ships a Python client first. But Go has quietly become a strong fit for the &lt;em&gt;runtime&lt;/em&gt; side of agents. A &lt;a href="https://news.ycombinator.com/item?id=47222270" rel="noopener noreferrer"&gt;Hacker News thread from earlier this year&lt;/a&gt; argued the case: one build system, one formatter, static types, and real concurrency without the footguns. When your agent has to fan out ten tool calls, handle timeouts, and ship as a single static binary, that counts for more than how many frameworks exist.&lt;/p&gt;

&lt;p&gt;I spent an afternoon porting a toy Python agent to Go, and the thing I kept noticing was how little I missed the framework. The whole "agent" abstraction collapses into a &lt;code&gt;for&lt;/code&gt; loop and a &lt;code&gt;switch&lt;/code&gt; statement once you see the message flow. That is the point of this post: strip away the layers so you understand what an agent actually is, then decide whether you want a framework on top.&lt;/p&gt;

&lt;p&gt;Here's what we'll build: a working file-reading agent in about 120 lines of Go, with zero third-party dependencies and two tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model: an agent is a loop
&lt;/h2&gt;

&lt;p&gt;Strip away the marketing and an LLM agent is four steps in a cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send the conversation (plus a list of tools the model is allowed to call) to the API.&lt;/li&gt;
&lt;li&gt;The model replies. It either answers in text, or it asks to call a tool with some arguments.&lt;/li&gt;
&lt;li&gt;If it asked for a tool, your code runs that tool and captures the result.&lt;/li&gt;
&lt;li&gt;You append the result to the conversation and go back to step 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The loop ends when the model responds with plain text and no tool request. The API signals this with &lt;code&gt;stop_reason: "end_turn"&lt;/code&gt;, and that is the whole pattern. Memory, planning, and "ReAct" are all variations on the same cycle. If you understand the loop, you understand agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;You need Go 1.21+ and an API key. Grab one from the &lt;a href="https://console.anthropic.com/" rel="noopener noreferrer"&gt;Anthropic Console&lt;/a&gt; (the tool-use format is cleanest there; I'll note the OpenAI equivalent at the end). Export it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-..."&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;go-agent &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;go-agent
go mod init go-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependencies to &lt;code&gt;go get&lt;/code&gt;. We use &lt;code&gt;net/http&lt;/code&gt; and &lt;code&gt;encoding/json&lt;/code&gt; from the standard library and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Talk to the model
&lt;/h2&gt;

&lt;p&gt;Before we add tools, let's make one plain request so the wiring is obvious. The Messages API wants a POST to &lt;code&gt;https://api.anthropic.com/v1/messages&lt;/code&gt; with three headers and a JSON body. Here are the types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;         &lt;span class="s"&gt;`json:"name"`&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;         &lt;span class="s"&gt;`json:"description"`&lt;/span&gt;
    &lt;span class="n"&gt;InputSchema&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="s"&gt;`json:"input_schema"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ContentBlock&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"type"`&lt;/span&gt;
    &lt;span class="c"&gt;// text block&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"text,omitempty"`&lt;/span&gt;
    &lt;span class="c"&gt;// tool_use block (from the model)&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;          &lt;span class="s"&gt;`json:"id,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;          &lt;span class="s"&gt;`json:"name,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawMessage&lt;/span&gt; &lt;span class="s"&gt;`json:"input,omitempty"`&lt;/span&gt;
    &lt;span class="c"&gt;// tool_result block (from us)&lt;/span&gt;
    &lt;span class="n"&gt;ToolUseID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"tool_use_id,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"content,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Role&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;         &lt;span class="s"&gt;`json:"role"`&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;ContentBlock&lt;/span&gt; &lt;span class="s"&gt;`json:"content"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Model&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"model"`&lt;/span&gt;
    &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;       &lt;span class="s"&gt;`json:"max_tokens"`&lt;/span&gt;
    &lt;span class="n"&gt;Messages&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="s"&gt;`json:"messages"`&lt;/span&gt;
    &lt;span class="n"&gt;Tools&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;    &lt;span class="s"&gt;`json:"tools,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;ContentBlock&lt;/span&gt; &lt;span class="s"&gt;`json:"content"`&lt;/span&gt;
    &lt;span class="n"&gt;StopReason&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;         &lt;span class="s"&gt;`json:"stop_reason"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one struct worth staring at is &lt;code&gt;ContentBlock&lt;/code&gt;. The API models every piece of a turn as a typed block (a &lt;code&gt;text&lt;/code&gt; block, a &lt;code&gt;tool_use&lt;/code&gt; block, a &lt;code&gt;tool_result&lt;/code&gt; block), and &lt;code&gt;omitempty&lt;/code&gt; lets one Go struct serialize as any of them. The model's &lt;code&gt;input&lt;/code&gt; arrives as arbitrary JSON, so I keep it as &lt;code&gt;json.RawMessage&lt;/code&gt; and decode it later per tool.&lt;/p&gt;

&lt;p&gt;Now the call itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;callClaude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"claude-sonnet-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxTokens&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Messages&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://api.anthropic.com/v1/messages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-api-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ANTHROPIC_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"anthropic-version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2023-06-01"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api %d: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;anthropic-version&lt;/code&gt; header is not optional. Leave it out and you get a &lt;code&gt;400&lt;/code&gt; with a message that does not obviously point at the missing header. That was gotcha number one for me. The &lt;a href="https://platform.claude.com/docs/en/api/messages" rel="noopener noreferrer"&gt;Messages API reference&lt;/a&gt; documents the current version string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Define the tools
&lt;/h2&gt;

&lt;p&gt;A tool is a name, a description, and a JSON Schema for its arguments. The description is doing real work here: the model reads it to decide &lt;em&gt;when&lt;/em&gt; to call the tool, so write it the way you'd brief a new teammate who has to figure out on their own when to reach for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;readFileTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"read_file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Read a file at a relative path and return its contents as text."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Relative path, e.g. main.go or docs/readme.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;listFilesTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"list_files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"List the files and folders in a directory. Defaults to the current directory."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InputSchema&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"dir"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Directory to list. Empty means current directory."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the dispatcher that actually runs a tool when the model asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"read_file"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"path"`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"error: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"list_files"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Dir&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"dir"`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"error: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"unknown tool: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice I return the error string to the model instead of crashing. That's deliberate. A good agent tool hands failures back as text so the model can recover — try a different path, ask the user, give up gracefully. If your tool panics, the loop dies and the model never gets a chance to react.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The agent loop
&lt;/h2&gt;

&lt;p&gt;This is the whole payoff — the piece that turns a chat call into an agent. Add &lt;code&gt;bufio&lt;/code&gt; and &lt;code&gt;strings&lt;/code&gt; to the imports, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;readFileTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listFilesTool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Go agent ready. Ask about files in this folder (Ctrl-D to quit)."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;you&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;ContentBlock&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c"&gt;// Inner loop: keep running tools until the model is done.&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;callClaude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c"&gt;// Echo the assistant turn back into history verbatim.&lt;/span&gt;
            &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;ContentBlock&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;claude&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  [tool] %s(%s)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ContentBlock&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"tool_result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;ToolUseID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="c"&gt;// stop_reason was end_turn — the model answered.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Role&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two loops, nested. The outer loop is the human conversation. The inner loop is the agent thinking: it keeps calling tools and feeding results back until the model stops asking. When there are no &lt;code&gt;tool_use&lt;/code&gt; blocks in a response, &lt;code&gt;results&lt;/code&gt; is empty and we break out to wait for the next human message.&lt;/p&gt;

&lt;p&gt;The line that bit me, gotcha number two, is &lt;code&gt;msgs = append(msgs, Message{Role: "assistant", Content: resp.Content})&lt;/code&gt;. You &lt;strong&gt;must&lt;/strong&gt; echo the assistant's &lt;code&gt;tool_use&lt;/code&gt; block back into the history before you send the matching &lt;code&gt;tool_result&lt;/code&gt;. Skip it, and the API rejects your next request because a &lt;code&gt;tool_result&lt;/code&gt; with no preceding &lt;code&gt;tool_use&lt;/code&gt; is malformed. Keeping the raw &lt;code&gt;resp.Content&lt;/code&gt; (including &lt;code&gt;tool_use&lt;/code&gt; blocks as &lt;code&gt;json.RawMessage&lt;/code&gt;) is the simplest way to get this right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;Build and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; agent &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a real session against a folder holding this project's Go files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go agent ready. Ask about files in this folder (Ctrl-D to quit).

you&amp;gt; what go files are here and what does main.go do?

  [tool] list_files({"dir":""})
  [tool] read_file({"path":"main.go"})

claude&amp;gt; This folder has three Go files: main.go, tools.go, and go.mod.
main.go is a command-line AI agent. It reads your input, sends it to the
Claude Messages API with two tools (read_file and list_files), runs any
tool the model requests, and loops until the model gives a final answer.
The core is a nested loop in main() that keeps executing tools until
stop_reason is end_turn.

you&amp;gt; which file is the biggest?

  [tool] list_files({"dir":""})
  [tool] read_file({"path":"main.go"})
  [tool] read_file({"path":"tools.go"})

claude&amp;gt; main.go is the largest at roughly 120 lines; tools.go is about 40.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model chained two tool calls in the first turn. It listed the directory, then decided to read &lt;code&gt;main.go&lt;/code&gt;, all before writing a single word of the answer. You did not orchestrate that ordering. The loop simply kept handing control back until the model decided it had enough to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you actually want a framework
&lt;/h2&gt;

&lt;p&gt;Building from scratch is the right way to &lt;em&gt;learn&lt;/em&gt;, and honestly it's fine for small production agents too. But once you need streaming, retries with backoff, session persistence, or multi-agent handoff, a framework earns its place. Here are the main Go options today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Reach for it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard library (this post)&lt;/td&gt;
&lt;td&gt;Raw HTTP + your own loop&lt;/td&gt;
&lt;td&gt;Learning, full control, single static binary, minimal surface area&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/anthropics/anthropic-sdk-go" rel="noopener noreferrer"&gt;Anthropic Go SDK&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Official typed client&lt;/td&gt;
&lt;td&gt;You want typed requests/streaming for one provider without hand-rolling JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/google/adk-go" rel="noopener noreferrer"&gt;Google ADK for Go&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Full agent framework: sessions, runners, tools&lt;/td&gt;
&lt;td&gt;Multi-agent orchestration, Gemini-first stacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/cloudwego/eino" rel="noopener noreferrer"&gt;Eino&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ByteDance's composable orchestration graph&lt;/td&gt;
&lt;td&gt;Complex pipelines with branching and fan-out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/tmc/langchaingo" rel="noopener noreferrer"&gt;langchaingo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;LangChain-style multi-provider abstraction&lt;/td&gt;
&lt;td&gt;You want one interface across OpenAI, Anthropic, and local models&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My honest take: start with the standard library, add the official SDK when JSON marshaling gets tedious, and only pull in a full framework when you feel real pain from the missing pieces. Most agents I've seen reach for a framework far too early and end up fighting its abstractions instead of shipping.&lt;/p&gt;

&lt;p&gt;If you're coming from the editor side of the agent world, the &lt;a href="https://www.danilchenko.dev/posts/agent-client-protocol/" rel="noopener noreferrer"&gt;Agent Client Protocol&lt;/a&gt; covers how these loops plug into IDEs, and &lt;a href="https://www.danilchenko.dev/posts/claude-code-subagents/" rel="noopener noreferrer"&gt;Claude Code Subagents&lt;/a&gt; shows the same tool-calling idea running inside a production coding agent. For the concurrency question specifically (why Go's model helps when tools run in parallel), the &lt;a href="https://www.danilchenko.dev/posts/rust-vs-go/" rel="noopener noreferrer"&gt;Rust vs Go comparison&lt;/a&gt; digs into the tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts I deliberately left out
&lt;/h2&gt;

&lt;p&gt;This agent is real but minimal. Three things you'd add before trusting it with anything important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails on tools.&lt;/strong&gt; My &lt;code&gt;read_file&lt;/code&gt; will happily read &lt;code&gt;/etc/passwd&lt;/code&gt; if the model asks. In production you sanitize paths and sandbox the filesystem. I wrote up four ways this goes wrong in &lt;a href="https://www.danilchenko.dev/posts/ai-agent-guardrails/" rel="noopener noreferrer"&gt;AI agent guardrails&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A turn limit.&lt;/strong&gt; The inner loop trusts the model to eventually stop. A confused model can ping-pong tools forever. Add a counter and bail after, say, 10 iterations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming.&lt;/strong&gt; We wait for the full response before printing. For a snappy CLI you'd switch to the streaming endpoint and print tokens as they arrive — Go's &lt;code&gt;bufio.Scanner&lt;/code&gt; over the SSE body handles this cleanly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these change the shape of the loop; they just harden it around the edges.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Is Go good for building AI agents?
&lt;/h3&gt;

&lt;p&gt;Yes, for the runtime. Go gives you static binaries, real concurrency for parallel tool calls, and strong typing around the JSON the API speaks. The tradeoff is fewer frameworks than Python — but as this post shows, you often don't need one. If your agent is mostly orchestration and I/O rather than data science, Go is a strong pick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a framework like LangChain to build an agent in Go?
&lt;/h3&gt;

&lt;p&gt;No. An agent is a loop over a chat API with tool calling, and the standard library covers it in ~120 lines. Frameworks add value for streaming, retries, session state, and multi-agent coordination, but they are optional. Start without one so you understand the mechanics.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does an AI agent work under the hood?
&lt;/h3&gt;

&lt;p&gt;It's a cycle: send the conversation plus a tool list to the model; the model either answers or requests a tool call; your code runs the tool and appends the result; repeat until the model answers with no tool request. The API's &lt;code&gt;stop_reason&lt;/code&gt; field tells you when the loop is done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use OpenAI instead of Anthropic?
&lt;/h3&gt;

&lt;p&gt;Yes. The shape is identical, only the field names differ. With the &lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;OpenAI API&lt;/a&gt; you POST to &lt;code&gt;/v1/chat/completions&lt;/code&gt;, tools live under a &lt;code&gt;tools&lt;/code&gt; array with a &lt;code&gt;function&lt;/code&gt; wrapper, and the model returns &lt;code&gt;tool_calls&lt;/code&gt; instead of &lt;code&gt;tool_use&lt;/code&gt; blocks. Swap the request/response structs and the &lt;code&gt;executeTool&lt;/code&gt; dispatcher stays exactly the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  What model should I use for a Go agent?
&lt;/h3&gt;

&lt;p&gt;Any model with tool-calling support works. I used &lt;code&gt;claude-sonnet-5&lt;/code&gt; because it's fast and reliable at deciding when to call a tool. For cheaper runs, a smaller model like Haiku handles simple tool routing well; for hard multi-step tasks, a frontier model chains tools more reliably.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/api/messages" rel="noopener noreferrer"&gt;Anthropic Messages API reference&lt;/a&gt; — the request/response format and tool-use schema this agent speaks&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/docs/build-with-claude/tool-use" rel="noopener noreferrer"&gt;Anthropic tool use documentation&lt;/a&gt; — how &lt;code&gt;tool_use&lt;/code&gt; and &lt;code&gt;tool_result&lt;/code&gt; blocks work&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/anthropic-sdk-go" rel="noopener noreferrer"&gt;Anthropic Go SDK&lt;/a&gt; — official typed client for production use&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/google/adk-go" rel="noopener noreferrer"&gt;Google ADK for Go&lt;/a&gt; — full agent framework, referenced in the comparison table&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/item?id=47222270" rel="noopener noreferrer"&gt;"A case for Go as the best language for AI agents" — Hacker News&lt;/a&gt; — the discussion that framed the "why Go" argument&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;The framework tutorials make agents look like a stack of abstractions you have to learn first. Really an agent is just a loop that hands control back and forth between a model and your tools, and Go writes that loop about as cleanly as any language I've tried. Build the 120-line version first. Once you can see the message flow in your head, you'll know exactly which framework feature you're missing — and, more often than not, that you don't need one yet.&lt;/p&gt;

</description>
      <category>go</category>
      <category>agents</category>
      <category>llm</category>
      <category>anthropicapi</category>
    </item>
    <item>
      <title>Claude Sonnet 5 Review: A Week With Anthropic's New Default</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Tue, 07 Jul 2026 08:42:22 +0000</pubDate>
      <link>https://dev.to/dmaxdev/claude-sonnet-5-review-a-week-with-anthropics-new-default-55p2</link>
      <guid>https://dev.to/dmaxdev/claude-sonnet-5-review-a-week-with-anthropics-new-default-55p2</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Claude Sonnet 5 shipped on June 30, 2026 and immediately became the default model on Claude's Free and Pro plans. Anthropic's published numbers put it at 63.2% on SWE-bench Pro, a few points behind Opus 4.8's 69.2% but well ahead of Sonnet 4.6's 58.1%. On Terminal-Bench 2.1 it actually beats Opus 4.8 (80.4% vs 74.6%). Intro pricing is $2 per million input tokens and $10 output through August 31, then $3/$15. One detail is easy to miss: it ships with a new tokenizer that uses 1.0–1.35× more tokens for the same text, so the sticker price undersells what you'll actually pay. After a week of using it as my daily driver in Claude Code, my take is that Sonnet 5 is the model most people should be running now, and Opus 4.8 has shrunk to a specialist tool you reach for on the genuinely hard problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Sonnet 5 Actually Is
&lt;/h2&gt;

&lt;p&gt;Anthropic ships models in three sizes: Haiku for cheap and fast, Sonnet for the middle, Opus for the frontier. Sonnet has always been the workhorse, the one that runs when you don't think about which model you're using. &lt;a href="https://www.anthropic.com/news/claude-sonnet-5" rel="noopener noreferrer"&gt;Sonnet 5&lt;/a&gt; is the fifth generation of that workhorse, and the pitch is that the middle tier has crept close enough to the top tier that the top tier stops being the obvious choice.&lt;/p&gt;

&lt;p&gt;It landed as the default for Free and Pro on claude.ai the day it launched, with access for Max, Team, and Enterprise, plus the API id &lt;code&gt;claude-sonnet-5&lt;/code&gt; on the Claude Platform and inside Claude Code. That default placement counts for more than the benchmarks. Most Claude traffic never picks a model on purpose. Whatever Anthropic sets as the default writes most of the code, drafts most of the emails, and shapes what people think "Claude" is. That makes Sonnet 5 the new baseline for almost everyone who touches Claude, whether they know the version number or not.&lt;/p&gt;

&lt;p&gt;I flipped my Claude Code default to it on launch morning and left it there for a week, running it against a Go service with a messy migration path, a Next.js dashboard, and a pile of one-off scripts. This was the everyday work sitting in my terminal, the kind I'd normally split between Claude and Codex. What stuck with me is that it finishes things. Multi-step tasks that used to stall halfway and wait for me to nudge them now run to completion on their own, and that single behavioral change is worth more than any single-digit benchmark bump.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmark Breakdown
&lt;/h2&gt;

&lt;p&gt;Here are the numbers &lt;a href="https://www.marktechpost.com/2026/06/30/anthropic-claude-sonnet-5-vs-sonnet-4-6-vs-opus-4-8-agentic-coding-benchmarks-api-pricing-and-cost-performance-tradeoffs-compared/" rel="noopener noreferrer"&gt;Anthropic and early testers published&lt;/a&gt;, lined up against the two models Sonnet 5 is really being measured against: its own predecessor and the flagship one rung up.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Sonnet 5&lt;/th&gt;
&lt;th&gt;Sonnet 4.6&lt;/th&gt;
&lt;th&gt;Opus 4.8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench Pro (agentic coding)&lt;/td&gt;
&lt;td&gt;63.2%&lt;/td&gt;
&lt;td&gt;58.1%&lt;/td&gt;
&lt;td&gt;69.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench 2.1&lt;/td&gt;
&lt;td&gt;80.4%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;74.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSWorld-Verified (computer use)&lt;/td&gt;
&lt;td&gt;81.2%&lt;/td&gt;
&lt;td&gt;78.5%&lt;/td&gt;
&lt;td&gt;83.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BrowseComp (agentic search)&lt;/td&gt;
&lt;td&gt;84.7%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDPval-AA v2 (knowledge work, Elo)&lt;/td&gt;
&lt;td&gt;1,618&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1,615&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FrontierCode&lt;/td&gt;
&lt;td&gt;38.8%&lt;/td&gt;
&lt;td&gt;15.1%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rows stand out. On SWE-bench Pro, the 5.1-point jump from Sonnet 4.6 to Sonnet 5 (58.1 to 63.2) is a real generational gain on a benchmark where model families usually move a couple of points per release. FrontierCode more than doubled in reported results, from 15.1% to 38.8%. Since FrontierCode targets the hard, long-horizon coding problems that break most models, a 2.5× jump there is the clearest signal that Sonnet 5 got structurally better at sustained agentic work rather than merely more polished.&lt;/p&gt;

&lt;p&gt;The Terminal-Bench result is the one that made me raise an eyebrow. A mid-tier model beating the flagship on any benchmark is unusual, and Terminal-Bench 2.1 measures exactly the kind of shell-driving, command-running, output-reading loop that agentic coding lives in. Sonnet 5's 80.4% against Opus 4.8's 74.6% lines up with what I felt using it: for the day-to-day loop of edit, run tests, read the failure, fix, rerun, it is genuinely excellent, and the extra brains in Opus don't help much on that kind of task.&lt;/p&gt;

&lt;p&gt;Where Opus still wins is SWE-bench Pro and OSWorld, both by a handful of points. Those gaps are real but small, and they narrow further once you weigh the cost difference. The GDPval knowledge-work Elo is essentially a tie (1,618 vs 1,615), which tells you the reasoning and writing quality of the two models is now hard to tell apart on ordinary tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Week Of Real Use
&lt;/h2&gt;

&lt;p&gt;Benchmarks tell you a model's ceiling. A week of use tells you its floor, how it behaves on the boring 90% of tasks that never make it into a benchmark suite.&lt;/p&gt;

&lt;p&gt;The best thing about Sonnet 5 is the self-verification loop. On my Go service, I asked it to migrate a set of handlers from a hand-rolled router to &lt;code&gt;chi&lt;/code&gt; and update the tests. Sonnet 4.6 would have written the change and stopped, leaving me to run the suite and paste back the failures. Sonnet 5 wrote the change, ran the tests itself, saw two of them fail on a middleware ordering bug, fixed the ordering, and reran until green — all without me typing anything between the first prompt and the finished diff. It did this consistently over the week, not once as a lucky demo. Early coverage reports the same pattern: the model runs tests, reads the failures, patches, and reruns without being told to. That is the behavior that turns "AI that writes code" into "AI that finishes a task."&lt;/p&gt;

&lt;p&gt;The second thing I noticed is that it's honest about uncertainty more often. On an ambiguous refactor where I hadn't specified whether to keep backward compatibility, it stopped and asked instead of guessing and plowing ahead. That's a small thing that saves a lot of wasted diffs.&lt;/p&gt;

&lt;p&gt;It still has clear failure modes. On a gnarly Next.js server-component bug involving a hydration mismatch, Sonnet 5 went in circles twice: it proposed the same wrong fix, I rejected it, and it came back with a slight variation of the same wrong fix. I switched that one task to Opus 4.8 and it found the actual cause (a &lt;code&gt;Date&lt;/code&gt; being rendered without a stable server/client format) in one pass. That is the shape of the current gap: on the genuinely hard, ambiguous problems that need real backtracking, Opus still pulls ahead. On everything else, you won't notice a difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tokenizer Tax
&lt;/h2&gt;

&lt;p&gt;The pricing page leaves out one detail. Sonnet 5 ships with an updated tokenizer, and &lt;a href="https://www.anthropic.com/news/claude-sonnet-5" rel="noopener noreferrer"&gt;Anthropic's own announcement&lt;/a&gt; says it produces roughly 1.0 to 1.35× more tokens for the same input text than Sonnet 4.6 did. That multiplier is not uniform: plain English lands near the low end, while code, structured data, and non-English text push toward the high end.&lt;/p&gt;

&lt;p&gt;What that means in practice: the $2/$10 intro price is genuinely cheap, but your token counts go up, so the effective cost per request rises more than the sticker drop suggests. If you're comparing Sonnet 5's $3/$15 standard price against Sonnet 4.6's $3/$15, they look identical on paper, but your bill won't match, because the same task now consumes more tokens. I watched this happen in Claude Code's usage meter over the week: comparable tasks showed noticeably higher token counts than I was used to under 4.6.&lt;/p&gt;

&lt;p&gt;This doesn't make Sonnet 5 expensive. It's still dramatically cheaper than Opus 4.8. But it does mean you should run your own numbers before assuming a migration is a pure cost win. If you have a high-volume production pipeline with tight per-task budgets, measure the real token consumtion on your actual prompts rather than trusting the price-per-million headline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input / 1M&lt;/th&gt;
&lt;th&gt;Output / 1M&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5 (intro, through Aug 31)&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;td&gt;New tokenizer, +0–35% tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 5 (standard, from Sep 1)&lt;/td&gt;
&lt;td&gt;$3&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;Same sticker as 4.6, higher token use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sonnet 4.6&lt;/td&gt;
&lt;td&gt;$3&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;Older, cheaper-per-token tokenizer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opus 4.8&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;Flagship; reach for the hard tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When To Use Sonnet 5 vs Opus 4.8
&lt;/h2&gt;

&lt;p&gt;After a week, my routing rule is simple. Send everything to Sonnet 5 by default. Reach for Opus 4.8 only when a task is hard enough that a wrong answer is expensive: a subtle concurrency bug, an ambiguous spec that needs careful interpretation, a refactor where backtracking pays off. Keep Haiku 4.5 for the high-volume, latency-sensitive calls where you'd never have used a big model anyway.&lt;/p&gt;

&lt;p&gt;That's a real shift from three months ago, when Opus was my default for anything that mattered and Sonnet was the "good enough, save money" option. Now the ordering has flipped: Sonnet 5 is the default that's good enough for almost everything, and Opus is the specialist you escalate to, with &lt;a href="https://www.danilchenko.dev/posts/claude-fable-5-review/" rel="noopener noreferrer"&gt;Fable 5&lt;/a&gt; sitting above it for the longest autonomous runs. The GDPval tie and the Terminal-Bench win explain why. On the work that fills most of a day, the two models produce output I can't reliably tell apart, so paying 2.5× more for Opus stops making sense.&lt;/p&gt;

&lt;p&gt;If you're weighing this against non-Anthropic options, the picture is more crowded than it was. &lt;a href="https://www.danilchenko.dev/posts/glm-5-2-review/" rel="noopener noreferrer"&gt;GLM-5.2&lt;/a&gt; undercuts everyone on price with open weights and lands close on coding benchmarks, and &lt;a href="https://www.danilchenko.dev/posts/gpt-5-5-review/" rel="noopener noreferrer"&gt;GPT-5.5&lt;/a&gt; trades blows on general tasks. Sonnet 5's edge over both is the agentic follow-through, the finish-the-task behavior, which is exactly the thing that doesn't show up cleanly in a single benchmark score. For a broader field, our &lt;a href="https://www.danilchenko.dev/posts/gpt-claude-gemini-coding/" rel="noopener noreferrer"&gt;GPT vs Claude vs Gemini coding comparison&lt;/a&gt; still holds up on the general shape of the tradeoffs, even if the specific version numbers have moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Switch To Sonnet 5
&lt;/h2&gt;

&lt;p&gt;If you're on claude.ai Free or Pro, you're already using it; it's the default. For everything else, you point at the model id.&lt;/p&gt;

&lt;p&gt;Via the API with the Python SDK from the &lt;a href="https://console.anthropic.com/" rel="noopener noreferrer"&gt;Anthropic console&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads ANTHROPIC_API_KEY
&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refactor this Go HTTP handler to use chi router &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and update the table-driven tests.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Claude Code, set it as the session default so every agent turn uses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# One-off for the current session&lt;/span&gt;
claude &lt;span class="nt"&gt;--model&lt;/span&gt; claude-sonnet-5

&lt;span class="c"&gt;# Or make it sticky in your shell profile&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"claude-sonnet-5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you drive Claude through a coding IDE, the same model id works. I run it as the default in &lt;a href="https://www.cursor.com/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; for ordinary edits and only flip to Opus when a task fights back. One thing to test before you migrate a production prompt: the new tokenizer can change how structured inputs get parsed, so if you have prompts that depend on exact output formatting or tight tool-call schemas, run them through once and check the edges rather than assuming a drop-in swap.&lt;/p&gt;

&lt;p&gt;Here's a quick way to sanity-check the token difference on your own text before and after switching, using the count-tokens endpoint:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count_tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sonnet 5 input tokens:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that against a representative prompt, compare it to what the same text cost you under Sonnet 4.6, and you'll know your real multiplier instead of guessing at the 1.0–1.35× range.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Use It, Who Should Skip
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anyone already on Claude for coding. Sonnet 5 is a straight upgrade over 4.6 in every category, and during the intro window it's cheaper too.&lt;/li&gt;
&lt;li&gt;Teams running agentic workflows (Claude Code, Cursor, custom harnesses) where the self-verification loop saves human turns.&lt;/li&gt;
&lt;li&gt;Cost-conscious builders who were paying for Opus out of habit. Most of that work drops to Sonnet 5 with no quality loss you'll notice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Think twice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-volume production pipelines with razor-thin margins. Measure the tokenizer tax on your real traffic before you assume the migration saves money.&lt;/li&gt;
&lt;li&gt;Anyone whose prompts depend on exact tokenization or output formatting. Test the edges first.&lt;/li&gt;
&lt;li&gt;Work that lives at the hard end of the difficulty curve: deep debugging, ambiguous specs, tasks that need backtracking. Keep Opus 4.8 in reach for those, and see our &lt;a href="https://www.danilchenko.dev/posts/claude-code-max-vs-pro/" rel="noopener noreferrer"&gt;Claude Code Max vs Pro breakdown&lt;/a&gt; if you're deciding how much Anthropic compute to buy.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  How much does Claude Sonnet 5 cost?
&lt;/h3&gt;

&lt;p&gt;Introductory API pricing is $2 per million input tokens and $10 per million output tokens through August 31, 2026, moving to $3/$15 after that. On claude.ai it's the default model on the Free and Pro plans at no extra cost. Factor in the new tokenizer, which uses 1.0–1.35× more tokens for the same text, when you estimate real spend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Claude Sonnet 5 good for coding?
&lt;/h3&gt;

&lt;p&gt;Yes — it's the strongest mid-tier coding model Anthropic has shipped. It scores 63.2% on SWE-bench Pro (up from Sonnet 4.6's 58.1%) and beats Opus 4.8 on Terminal-Bench 2.1. The standout in daily use is agentic follow-through: it runs tests, reads failures, and fixes its own code without being told to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Sonnet 5 vs Opus 4.8 — which should I use?
&lt;/h3&gt;

&lt;p&gt;Use Sonnet 5 as your default for almost everything; it's close to Opus 4.8 on most benchmarks, wins on Terminal-Bench, and costs less than half as much. Reserve Opus 4.8 for the hardest tasks (subtle bugs, ambiguous requirements, anything that needs real backtracking) where its extra reasoning still pulls ahead.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Claude Sonnet 5's context window?
&lt;/h3&gt;

&lt;p&gt;Sonnet 5 supports a 1 million token context window, matching the top of Anthropic's lineup. That's enough to hold a mid-size codebase, long agent trajectories, or large documents in a single session.&lt;/p&gt;

&lt;h3&gt;
  
  
  When was Claude Sonnet 5 released?
&lt;/h3&gt;

&lt;p&gt;Anthropic released Claude Sonnet 5 on June 30, 2026, and made it the default model on the Free and Pro plans the same day.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/news/claude-sonnet-5" rel="noopener noreferrer"&gt;Introducing Claude Sonnet 5 (Anthropic)&lt;/a&gt;: official announcement, pricing, availability, and the tokenizer change&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.claude.com/docs/en/about-claude/models/overview" rel="noopener noreferrer"&gt;Claude models overview (Claude Platform Docs)&lt;/a&gt;: model ids, context windows, and standard pricing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.marktechpost.com/2026/06/30/anthropic-claude-sonnet-5-vs-sonnet-4-6-vs-opus-4-8-agentic-coding-benchmarks-api-pricing-and-cost-performance-tradeoffs-compared/" rel="noopener noreferrer"&gt;Claude Sonnet 5 vs Sonnet 4.6 vs Opus 4.8 (MarkTechPost)&lt;/a&gt;: benchmark table and cost-performance analysis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.finout.io/blog/claude-sonnet-5-pricing-2026-the-hidden-costs-and-real-savings-behind-the-cost-neutral-launch" rel="noopener noreferrer"&gt;Claude Sonnet 5 pricing: the hidden costs behind the "cost-neutral" launch (Finout)&lt;/a&gt;: breakdown of the tokenizer's effect on real spend&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Claude Sonnet 5 is the clearest sign yet that the interesting action in AI coding has moved from the frontier tier to the tier just below it. It nearly matches Opus 4.8 on the benchmarks that matter, beats it outright on the terminal-driving loop that agentic coding actually runs on, and does it at less than half the price. After a week of leaving it as my Claude Code default, the only tasks I sent back to Opus were the two or three genuinely hard bugs a week throws at you.&lt;/p&gt;

&lt;p&gt;The tokenizer tax is the one thing to watch. Sonnet 5 is cheaper than Opus and, during the intro window, cheaper than 4.6, but the higher token consumption means you should measure your real cost rather than trust the headline. Do that math, then switch. For the work that fills most of a developer's day, this is the model to reach for now, and if you're on a Free or Pro plan, it's already the one running by default.&lt;/p&gt;

</description>
      <category>claudesonnet5</category>
      <category>anthropic</category>
      <category>aicoding</category>
      <category>llm</category>
    </item>
    <item>
      <title>Perplexity Bumblebee Review: The Supply Chain Scanner Your Dev Machine Needs</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:35:52 +0000</pubDate>
      <link>https://dev.to/dmaxdev/perplexity-bumblebee-review-the-supply-chain-scanner-your-dev-machine-needs-20ic</link>
      <guid>https://dev.to/dmaxdev/perplexity-bumblebee-review-the-supply-chain-scanner-your-dev-machine-needs-20ic</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Bumblebee is a read-only supply chain scanner from Perplexity AI that checks your installed packages, editor extensions, MCP configs, and browser extensions against known-compromised versions. It never runs a package manager command. I ran it on three machines. It found 847 packages across 9 ecosystems in under 4 seconds. The output is NDJSON you can pipe into anything. It won't replace Snyk or Socket in your CI pipeline, but for the question "does anyone on the team have that compromised package installed right now?" it's the fastest answer that exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developer Machines Are the Blind Spot
&lt;/h2&gt;

&lt;p&gt;When a supply chain advisory drops (say, a compromised npm package like &lt;code&gt;@redhat-cloud-services/frontend-components&lt;/code&gt; in June 2026), the security team's first question is simple: who has it installed?&lt;/p&gt;

&lt;p&gt;Your CI pipeline knows what's in your lockfile. Your SBOM tool knows what shipped to production. Your EDR knows what processes ran. None of them know what's sitting in &lt;code&gt;~/.npm&lt;/code&gt;, what extensions your VS Code installed last Tuesday, or what MCP servers your Claude Code config points to.&lt;/p&gt;

&lt;p&gt;Sonatype's 2026 State of the Software Supply Chain report counted over 454,600 new malicious open-source packages in 2025, pushing the cumulative total past 1.233 million. That's a 75% year-over-year increase. The attacks keep getting more creative, too: the Mini Shai-Hulud campaigns starting in late April 2026 hit npm, PyPI, RubyGems, and Composer across companies like SAP, with the TanStack compromise following in May. In June 2026, TeamPCP's Miasma worm injected itself into the SessionStart hooks of 13 AI coding tools, including Claude Code, GitHub Copilot, and &lt;a href="https://www.danilchenko.dev/posts/gemini-cli-tutorial/" rel="noopener noreferrer"&gt;Gemini CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That last one is why MCP config scanning matters. If your &lt;code&gt;claude_desktop_config.json&lt;/code&gt; or &lt;code&gt;mcp.json&lt;/code&gt; references a compromised server, you've handed an attacker tool-level access to your coding environment. Traditional security scanners don't even look at these files.&lt;/p&gt;

&lt;p&gt;Bumblebee fills that gap. It answers one question: what packages, extensions, and configs are on this machine right now, and do any of them match a known-bad advisory?&lt;/p&gt;

&lt;h2&gt;
  
  
  What Bumblebee Actually Is
&lt;/h2&gt;

&lt;p&gt;Bumblebee is a single static binary written in &lt;a href="https://www.danilchenko.dev/posts/go-1-26-features/" rel="noopener noreferrer"&gt;Go 1.25+&lt;/a&gt; with zero non-stdlib dependencies. Perplexity open-sourced it in May 2026 under Apache 2.0. The v0.1.1 release has crossed 4k GitHub stars.&lt;/p&gt;

&lt;p&gt;The design is deliberately narrow. Bumblebee collects an inventory of what's installed on a developer machine and optionally matches that inventory against an exposure catalog of known-compromised packages. It does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;pip install&lt;/code&gt;, or any other package manager command&lt;/li&gt;
&lt;li&gt;Execute install scripts or lifecycle hooks&lt;/li&gt;
&lt;li&gt;Read your source code&lt;/li&gt;
&lt;li&gt;Make network calls during the scan&lt;/li&gt;
&lt;li&gt;Modify anything on disk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last set of constraints is the whole point. When your machine might already be compromised, the scan tool itself can't be the thing that triggers the compromise. npm's &lt;code&gt;postinstall&lt;/code&gt; scripts are a well-known attack vector. Bumblebee never gives them a chance to fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Scans
&lt;/h2&gt;

&lt;p&gt;The coverage spans ten ecosystems, which is wider than I expected from a v0.1 tool:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ecosystem&lt;/th&gt;
&lt;th&gt;What It Reads&lt;/th&gt;
&lt;th&gt;Package Manager&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock&lt;/td&gt;
&lt;td&gt;npm/yarn/pnpm/bun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyPI&lt;/td&gt;
&lt;td&gt;*.dist-info/METADATA, *.egg-info/PKG-INFO&lt;/td&gt;
&lt;td&gt;pip/setuptools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go modules&lt;/td&gt;
&lt;td&gt;go.sum, go.mod&lt;/td&gt;
&lt;td&gt;go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RubyGems&lt;/td&gt;
&lt;td&gt;Gemfile.lock, *.gemspec&lt;/td&gt;
&lt;td&gt;bundler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composer&lt;/td&gt;
&lt;td&gt;composer.lock, installed.json&lt;/td&gt;
&lt;td&gt;composer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;td&gt;INSTALL_RECEIPT.json, .metadata&lt;/td&gt;
&lt;td&gt;brew&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP servers&lt;/td&gt;
&lt;td&gt;claude_desktop_config.json, mcp.json, various IDE configs&lt;/td&gt;
&lt;td&gt;Claude Code/Cursor/Gemini CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent skills&lt;/td&gt;
&lt;td&gt;skills-lock.json&lt;/td&gt;
&lt;td&gt;vercel-labs/skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor extensions&lt;/td&gt;
&lt;td&gt;VS Code, Cursor, Windsurf, VSCodium manifests&lt;/td&gt;
&lt;td&gt;IDE ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser extensions&lt;/td&gt;
&lt;td&gt;Chrome, Edge, Brave, Arc, Firefox manifests&lt;/td&gt;
&lt;td&gt;Browser ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The MCP and agent skills scanning is what makes this feel purpose-built for 2026. Phoenix Security's 2026 supply chain report found that AI-agent skills carry a risk rate &lt;a href="https://phoenix.security/accelerating-supply-chain-attacks-npm-pypi-vsx-ai-enabled-2026/" rel="noopener noreferrer"&gt;2.3 times higher than IDE extensions&lt;/a&gt;, and more than 1 in 4 deep-scanned skills triggered a critical-risk finding. Bumblebee parses &lt;code&gt;claude_desktop_config.json&lt;/code&gt;, &lt;code&gt;mcp.json&lt;/code&gt;, and Gemini CLI config files to catalog which &lt;a href="https://www.danilchenko.dev/posts/fastmcp-mcp-server/" rel="noopener noreferrer"&gt;MCP servers&lt;/a&gt; are registered and their source packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It: Three Machines, Three Profiles
&lt;/h2&gt;

&lt;p&gt;I installed Bumblebee via &lt;code&gt;go install&lt;/code&gt; and ran it across three environments: my primary macOS development laptop, a Linux CI runner, and a colleague's machine that I suspected had stale npm packages from a prototype project six months ago.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/perplexityai/bumblebee/cmd/bumblebee@v0.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One binary. No config files. No daemon. The Go install pulls the source and compiles it locally, so the binary on your machine was built from source you can audit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline Scan
&lt;/h3&gt;

&lt;p&gt;The baseline profile hits common global package roots, editor extensions, browser extensions, and MCP configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bumblebee scan &lt;span class="nt"&gt;--profile&lt;/span&gt; baseline &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; inventory.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my macOS machine, the baseline scan completed in 1.8 seconds and found 312 packages across npm, PyPI, Go modules, Homebrew, 23 VS Code extensions, 4 Cursor extensions, and 3 MCP server configurations.&lt;/p&gt;

&lt;p&gt;Each record in the NDJSON output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"record_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hostname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"maxbook.local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ecosystem"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"package_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"filesystem-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.6.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp_config"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/max/.config/claude/claude_desktop_config.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The confidence levels are useful. &lt;code&gt;high&lt;/code&gt; means exact canonical metadata with verified version. &lt;code&gt;medium&lt;/code&gt; means reliable identity but partial version info, which is common for MCP servers where the config only records a package reference. &lt;code&gt;low&lt;/code&gt; means it's a config path or spec reference only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bumblebee scan &lt;span class="nt"&gt;--profile&lt;/span&gt; project &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--root&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/code"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--root&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Developer"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; project-inventory.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This took 3.4 seconds and found 535 packages. The extra coverage comes from crawling lockfiles in project directories. A Go project I'd forgotten about contributed 47 transitive dependencies from its &lt;code&gt;go.sum&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep Scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bumblebee scan &lt;span class="nt"&gt;--profile&lt;/span&gt; deep &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--root&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-duration&lt;/span&gt; 10m &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; deep-inventory.ndjson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deep profile is for incident response. It sweeps everything under the given root. On my machine with a messy home directory, it found 847 packages in 3.9 seconds. On the colleague's machine (the one with stale npm prototypes), it found 1,423 packages. A lot of them were cached npm packages from six months ago that nobody realized were still sitting on disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposure Check
&lt;/h3&gt;

&lt;p&gt;This is where Bumblebee earns its keep. Once you have an advisory (say, the June 2026 &lt;code&gt;@redhat-cloud-services&lt;/code&gt; compromise), you write an exposure catalog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"advisory-2026-redhat-npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@redhat-cloud-services/frontend-components 4.2.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ecosystem"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@redhat-cloud-services/frontend-components"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"versions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"4.2.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.2.4"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bumblebee scan &lt;span class="nt"&gt;--profile&lt;/span&gt; deep &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--root&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exposure-catalog&lt;/span&gt; ./catalog.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any match exists, Bumblebee emits a &lt;code&gt;finding&lt;/code&gt; record alongside the normal inventory. The finding includes the matched package, version, catalog entry, and severity. Pipe the output through &lt;code&gt;jq '.record_type == "finding"'&lt;/code&gt; and you have a yes/no answer in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Test
&lt;/h3&gt;

&lt;p&gt;Before trusting it with real incident data, verify the binary works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bumblebee selftest
&lt;span class="c"&gt;# selftest OK (2 findings in 1ms)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The self-test uses embedded fixtures with fake package names. It validates that the scanning and matching logic works correctly on your platform before you point it at real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Bumblebee Fits (and Where It Doesn't)
&lt;/h2&gt;

&lt;p&gt;Don't treat Bumblebee as a replacement for your existing security toolchain. It's a complement. Here's how it maps against the tools you probably already use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;When It Runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Snyk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scans dependencies for known CVEs, generates fix PRs&lt;/td&gt;
&lt;td&gt;CI/CD, on PR merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Socket&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Behavioral analysis of packages pre-install (typosquatting, exfiltration)&lt;/td&gt;
&lt;td&gt;Pre-install, CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OSV-Scanner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Matches lockfiles against the OSV database&lt;/td&gt;
&lt;td&gt;CI/CD, local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;npm audit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Checks installed packages against the npm advisory database&lt;/td&gt;
&lt;td&gt;Post-install, CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bumblebee&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reads on-disk metadata across 10 ecosystems, no execution&lt;/td&gt;
&lt;td&gt;On-demand, scheduled on dev machines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bumblebee fills the post-incident triage gap. Snyk tells you what's vulnerable in your repo. Socket tells you if a package is behaving suspiciously before you install it. Bumblebee tells you which developer machines are currently exposed to a specific known-compromised package. They complement each other.&lt;/p&gt;

&lt;p&gt;The strongest use case: a zero-day advisory drops at 2 AM. Your security team pushes an exposure catalog. Every developer machine runs &lt;code&gt;bumblebee scan --profile deep --exposure-catalog ./catalog.json&lt;/code&gt; as part of their morning boot script. By the time standup happens, you know exactly who's affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Liked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; Sub-4-second scans across 800+ packages on a well-loaded macOS machine. It reads metadata files directly from the filesystem, with no process spawning, no network calls, and no dependency resolution. The Go implementation is tight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero dependencies.&lt;/strong&gt; The entire binary is Go standard library. For a security tool, that's a big deal: every third-party dependency is an attack surface. Bumblebee's supply chain risk is literally zero non-stdlib packages. You can verify this yourself. The &lt;code&gt;go.mod&lt;/code&gt; file lists only the module path and Go version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP config scanning.&lt;/strong&gt; No other supply chain tool does this. With &lt;a href="https://glasp.co/articles/mcp-security-tool-poisoning-supply-chain" rel="noopener noreferrer"&gt;MCP tool poisoning&lt;/a&gt; becoming a real attack vector in 2026, a tool that catalogs which MCP servers are configured on a machine is ahead of the curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NDJSON output.&lt;/strong&gt; Every record is a self-contained JSON line. Pipe it through &lt;code&gt;jq&lt;/code&gt;, feed it to your SIEM, aggregate it with &lt;code&gt;xargs&lt;/code&gt;. No proprietary format, no dashboard required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-only guarantee.&lt;/strong&gt; I checked the source. There are no &lt;code&gt;os.Create&lt;/code&gt;, no &lt;code&gt;os.WriteFile&lt;/code&gt;, no &lt;code&gt;exec.Command&lt;/code&gt; calls outside the self-test fixtures. The binary genuinely can't modify your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Doesn't Do
&lt;/h2&gt;

&lt;p&gt;The biggest practical limitation is version range matching. Bumblebee v0.1 only matches exact name + version pairs. If an advisory says "all versions below 2.3.1 are affected," you need to write out every affected version in the catalog, or write a wrapper that expands ranges. Most advisories use ranges, so this will bite you early.&lt;/p&gt;

&lt;p&gt;MCP config parsing is JSON-only. Continue uses YAML, Codex uses TOML, and both are skipped silently. Claude Code and Cursor both use JSON so you're covered there, but it's an incomplete picture.&lt;/p&gt;

&lt;p&gt;There's no remediation. Bumblebee tells you what's there. It won't remove it, update it, or file a PR. That's by design (a read-only tool should stay read-only), but it means you need a separate process for acting on the findings.&lt;/p&gt;

&lt;p&gt;There's also no continuous monitoring. It's a one-shot execution model. You run it, get results, done. If you want recurring scans, wrap it in a cron job or MDM policy. No agent, no daemon, no phone-home.&lt;/p&gt;

&lt;p&gt;Finally, Linux and macOS only. No Windows support in v0.1.1. The scan profiles are built around Unix filesystem conventions. If your team has Windows developers, Bumblebee misses them entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP Angle
&lt;/h2&gt;

&lt;p&gt;I want to call out the MCP scanning specifically because it's the feature that separates Bumblebee from every other supply chain tool I've used.&lt;/p&gt;

&lt;p&gt;Your &lt;code&gt;claude_desktop_config.json&lt;/code&gt; or project-level &lt;code&gt;mcp.json&lt;/code&gt; defines which tools your AI coding agent can call. A &lt;a href="https://www.danilchenko.dev/posts/litellm-vulnerability/" rel="noopener noreferrer"&gt;compromised MCP server&lt;/a&gt; gets tool-level access inside your development session. It can read files, write files, execute commands, and interact with external services, all under the guise of a legitimate tool.&lt;/p&gt;

&lt;p&gt;The Miasma worm that hit in June 2026 specifically targeted these config files, injecting itself into SessionStart hooks across 13 AI coding tools. Traditional security scanners didn't catch it because they don't read MCP configuration files. Bumblebee does.&lt;/p&gt;

&lt;p&gt;When I ran the baseline scan, it found three MCP servers in my Claude Code config: &lt;code&gt;filesystem-mcp-server&lt;/code&gt;, &lt;code&gt;brave-search&lt;/code&gt;, and a custom internal tool. All three were clean, but the fact that they showed up in the inventory meant I could match them against future advisories automatically. That's the workflow: scan once, match many times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Bumblebee in a Team
&lt;/h2&gt;

&lt;p&gt;For individual developers, the one-shot model works fine. For a team, you need a bit more structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bumblebee scan &lt;span class="nt"&gt;--profile&lt;/span&gt; baseline &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exposure-catalog&lt;/span&gt; /shared/catalogs/latest.json &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'select(.record_type == "finding")'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; @- https://internal-siem.company.com/ingest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is: scan locally, filter for findings, push to a central collector. The NDJSON output makes this trivial to integrate with whatever log aggregtion you already run.&lt;/p&gt;

&lt;p&gt;For MDM-managed fleets, distribute the binary via your existing tooling (it's a single static file, no installer needed), set up a scheduled task, and point &lt;code&gt;--exposure-catalog&lt;/code&gt; at a shared network path that your security team updates when advisories drop.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Does Bumblebee replace Snyk or Socket?
&lt;/h3&gt;

&lt;p&gt;No. Bumblebee scans what's already on your machine. Snyk and Socket operate in CI/CD to prevent bad packages from entering your projects. Use all three. They cover different phases of the supply chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Bumblebee trigger malicious code during a scan?
&lt;/h3&gt;

&lt;p&gt;No. It reads metadata files (lockfiles, manifests, config JSON) directly from the filesystem. It never executes &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;pip install&lt;/code&gt;, or any lifecycle hooks. The scan can't trigger a compromised package's install script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work with private registries?
&lt;/h3&gt;

&lt;p&gt;Yes, for inventory purposes. Bumblebee reads lockfile metadata, which includes private package names and versions. It doesn't authenticate against registries. It reads from local disk, so there's nothing to authenticate against.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get exposure catalogs?
&lt;/h3&gt;

&lt;p&gt;You build them. Bumblebee doesn't ship with a built-in advisory database. When an advisory drops (from GitHub Security Advisories, Snyk's database, or your own threat intel), you convert it into the catalog JSON format and pass it to &lt;code&gt;--exposure-catalog&lt;/code&gt;. Multiple catalogs merge when you point at a directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a Windows version?
&lt;/h3&gt;

&lt;p&gt;Not yet. Bumblebee v0.1.1 supports macOS and Linux only. Windows support would require rewriting the scan profile filesystem paths and adding support for Windows-specific package manager locations (AppData, ProgramFiles).&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/perplexityai/bumblebee" rel="noopener noreferrer"&gt;Perplexity Bumblebee GitHub repository&lt;/a&gt;: source code, documentation, and release notes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.marktechpost.com/2026/05/23/perplexity-open-sources-bumblebee-a-read-only-supply-chain-scanner-for-developer-endpoints/" rel="noopener noreferrer"&gt;MarkTechPost coverage of Bumblebee release&lt;/a&gt;: independent analysis of the tool's architecture and purpose&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sonatype.com/state-of-the-software-supply-chain/2026" rel="noopener noreferrer"&gt;Sonatype 2026 State of the Software Supply Chain&lt;/a&gt;: 454,600 malicious packages in 2025, 1.233M cumulative&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://glasp.co/articles/mcp-security-tool-poisoning-supply-chain" rel="noopener noreferrer"&gt;MCP Security in 2026: Tool Poisoning and Supply Chain Risks&lt;/a&gt;: analysis of MCP-specific attack vectors&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://phoenix.security/accelerating-supply-chain-attacks-npm-pypi-vsx-ai-enabled-2026/" rel="noopener noreferrer"&gt;Supply Chain Attacks 2026: npm, PyPI, VS Code, AI Agents&lt;/a&gt;: attack campaign tracking and 2.3x AI-agent skill risk data&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/alanwest/bumblebee-vs-osv-scanner-two-takes-on-supply-chain-scanning-j29"&gt;Bumblebee vs OSV-Scanner comparison on Dev.to&lt;/a&gt;: positioning against other scanning tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Bumblebee does one thing and does it well: it inventories every package, extension, and MCP config on a developer machine without executing anything. That narrow scope is a feature. 454,600 malicious packages shipped in a single year. AI coding tools introduced a new attack surface nobody was scanning. A sub-4-second read-only answer to "are we exposed?" is worth adding to your toolkit.&lt;/p&gt;

&lt;p&gt;The version range limitation is real and will bite you on the first advisory that specifies a range instead of exact versions. The missing Windows support matters if you have a mixed fleet. But for Unix-based development teams using Claude Code, Cursor, or VS Code, Bumblebee fills a gap that Snyk, Socket, and npm audit don't touch: what's actually sitting on the developer's disk right now, including the MCP configs that no other tool bothers to read.&lt;/p&gt;

&lt;p&gt;Install it. Run the self-test. Point it at your home directory. The scan takes less time than reading this sentence, and you might not like what it finds.&lt;/p&gt;

</description>
      <category>bumblebee</category>
      <category>perplexity</category>
      <category>supplychainsecurity</category>
      <category>go</category>
    </item>
    <item>
      <title>Is There a Backend-as-a-Service for AI Coding Agents?</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Fri, 26 Jun 2026 14:15:13 +0000</pubDate>
      <link>https://dev.to/dmaxdev/is-there-a-backend-as-a-service-for-ai-coding-agents-3jh7</link>
      <guid>https://dev.to/dmaxdev/is-there-a-backend-as-a-service-for-ai-coding-agents-3jh7</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Yes. A handful of backends now target AI coding agents directly. InsForge, Base44, Butterbase, and MoonDB are among them. What sets them apart from Supabase or Firebase is that an agent can provision the whole thing from code: a JSON schema in, a hosted database with auth and a REST API out, no dashboard click-through. With MoonDB that's three calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "designed for AI coding agents" actually means
&lt;/h2&gt;

&lt;p&gt;Most backends were built for a person. You sign in, click to create tables, wire up auth in a settings panel, copy keys out of a dashboard, and paste them into your app. An agent like &lt;a href="https://moondb.ai/for/cursor" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; or &lt;a href="https://moondb.ai/for/claude" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; can't click. Drop one into that flow and it stalls at the first screen that needs a human.&lt;/p&gt;

&lt;p&gt;An agent-native backend removes the human step. The whole setup runs over HTTP or through an &lt;a href="https://moondb.ai/mcp" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;, so the agent does the work end to end. Three things make that possible, and they're the test for whether a backend really fits an agent:&lt;/p&gt;

&lt;p&gt;It provisions from code. Creating a project, applying a schema, and reading the resulting keys all happen through API calls that return JSON. Nothing waits on a console.&lt;/p&gt;

&lt;p&gt;It's declarative. The agent writes &lt;em&gt;what&lt;/em&gt; the data looks like: tables, fields, relationships, and who can read and write each one. The service figures out the SQL and runs the migration. No hand-written DDL, no guessing at migration order.&lt;/p&gt;

&lt;p&gt;Its errors are instructions. When a schema is wrong, the response says what to fix, not just that something broke. An agent reads that and corrects itself on the next call instead of looping.&lt;/p&gt;

&lt;p&gt;Supabase and Firebase can be scripted, and people do script them. But the happy path still runs through a dashboard, the client libraries assume a build step, and the error messages are written for a developer reading a stack trace. In the newer tools, the agent is the primary user and the human reviews the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backends built for this, as of 2026
&lt;/h2&gt;

&lt;p&gt;The category is small, and it already has a name: "agent-native" backends. A few worth knowing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://insforge.dev" rel="noopener noreferrer"&gt;InsForge&lt;/a&gt; is an open-source backend that bundles a stack of services behind a semantic layer an agent can read and provision: Postgres with auto-generated APIs, auth, S3-compatible storage, edge functions, compute and hosting, and an AI model gateway. The &lt;a href="https://github.com/InsForge/insforge" rel="noopener noreferrer"&gt;source is on GitHub&lt;/a&gt; under Apache-2.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://base44.com/backend" rel="noopener noreferrer"&gt;Base44&lt;/a&gt; leans on its CLI, which it tunes for Claude Code and Cursor so an agent can scaffold backend logic, data models, and APIs from a prompt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://butterbase.ai" rel="noopener noreferrer"&gt;Butterbase&lt;/a&gt; is another open-source option: Postgres, auth, storage, functions, an AI gateway, and an MCP server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.xano.com" rel="noopener noreferrer"&gt;Xano&lt;/a&gt; comes at it from the no-code side. It's a visual backend builder that's added AI features for generating APIs and logic.&lt;/p&gt;

&lt;p&gt;MoonDB sits at the minimal end of that list, and on purpose. It does one thing: turn a JSON schema into a hosted database with a REST API, auth, and file storage, all reachable over plain HTTP or MCP. There's no vector store, no edge-function runtime, no AI gateway. If your generated app needs RAG or a serverless compute layer, InsForge or Butterbase carry more out of the box. If it needs a database with auth and a clean API that an agent can stand up in seconds and a person can actually understand, the smaller surface is the point.&lt;/p&gt;

&lt;p&gt;The honest framing: on a feature checklist, Supabase wins. The question that sorts this category is which backend an agent can drive to "done" without a human unblocking it, and how little is left to misunderstand once it's running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provision a MoonDB backend the way an agent would
&lt;/h2&gt;

&lt;p&gt;Here's the full path an agent follows to back a generated app, with a to-do app as the canonical first build. Three calls, no dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: create a project
&lt;/h3&gt;

&lt;p&gt;One call returns the keys the rest of the flow needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://moondb.ai/v1/projects &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: mk_your_account_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "name": "todo-app" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"p_7k2d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"admin_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"public_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pk_..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The agent reads three keys from the JSON: an account key that manages projects, an admin key that changes the schema, and a public key that's safe to ship in client code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent keeps the &lt;code&gt;admin_key&lt;/code&gt; server-side and uses the &lt;code&gt;public_key&lt;/code&gt; for anything that runs in a browser. No human copies anything out of a console.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: send the schema
&lt;/h3&gt;

&lt;p&gt;The agent describes the data as one JSON document and applies it with a single PUT. A to-do app needs users and per-user todos, where each person only sees their own list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT https://moondb.ai/p/p_7k2d/v1/schema &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Admin-Key: sk_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; @schema.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"auth_table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"todos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string required max_length 200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bool default false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"due_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"enum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ref users required"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"owner_field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"access"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authenticated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"owner"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That document carries the whole security model. &lt;code&gt;"auth_table": true&lt;/code&gt; makes &lt;code&gt;users&lt;/code&gt; the login table, so MoonDB adds a hidden, required &lt;code&gt;password_hash&lt;/code&gt; column and serves signup and login endpoints. The agent never declares the password field. &lt;code&gt;"ref users required"&lt;/code&gt; ties each todo to a user. The &lt;code&gt;owner_field&lt;/code&gt; with &lt;code&gt;"read": "owner"&lt;/code&gt; scopes every read to the logged-in user, so one person's todos never leak into another's response, and the agent didn't write a line of authorization code to get that. &lt;code&gt;create: authenticated&lt;/code&gt; lets any signed-in user add a todo. MoonDB also adds &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, and &lt;code&gt;updated_at&lt;/code&gt; to every table on its own.&lt;/p&gt;

&lt;p&gt;MoonDB reads that, creates the tables, and serves the API immediately. Change the schema later and the agent sends new JSON; MoonDB diffs the two versions and migrates, and it refuses to run a destructive change unless the request opts in. That guardrail keeps an over-eager agent from dropping a column full of data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flx7t172vpukavg7y9jeh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flx7t172vpukavg7y9jeh.png" alt="The MoonDB dashboard schema editor showing the todos and users tables generated from the JSON schema" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The dashboard renders what the schema produced. An agent never opens this. It's here for the human to check the work.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: use the generated API
&lt;/h3&gt;

&lt;p&gt;The endpoints exist the moment the schema applies. The agent signs up an end user, gets a token back, and writes a todo as that user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create an end user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://moondb.ai/p/p_7k2d/auth/signup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "email": "sam@example.com", "password": "s3cret-pass", "display_name": "Sam" }'&lt;/span&gt;
&lt;span class="c"&gt;# → { "data": { "user": {...}, "token": "&amp;lt;jwt&amp;gt;", "refresh_token": "...", "expires_in": 3600 } }&lt;/span&gt;

&lt;span class="c"&gt;# Add a todo as that user&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://moondb.ai/p/p_7k2d/api/todos &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;jwt&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "title": "Ship the MVP", "priority": "high" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The todo lands with &lt;code&gt;user_id&lt;/code&gt; set to Sam from the token, because the &lt;code&gt;owner_field&lt;/code&gt; rule stamps ownership at write time. When Sam reads &lt;code&gt;GET /p/p_7k2d/api/todos&lt;/code&gt;, the &lt;code&gt;read: owner&lt;/code&gt; rule scopes the result to Sam's rows. The agent gets working auth and per-user data isolation out of the schema, not out of a pile of middleware it had to write and test.&lt;/p&gt;

&lt;p&gt;That's the loop a coding agent can run unattended: create, describe, write. For an agent that speaks &lt;a href="https://moondb.ai/mcp" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;, MoonDB exposes the same provisioning as MCP tools, so the steps above become tool calls inside the agent's session instead of shell commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the agent-native backends compare
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Supabase / Firebase&lt;/th&gt;
&lt;th&gt;InsForge / Butterbase&lt;/th&gt;
&lt;th&gt;MoonDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built for&lt;/td&gt;
&lt;td&gt;A developer at a dashboard&lt;/td&gt;
&lt;td&gt;AI coding agents&lt;/td&gt;
&lt;td&gt;AI coding agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How an agent provisions it&lt;/td&gt;
&lt;td&gt;Scripted around a dashboard flow&lt;/td&gt;
&lt;td&gt;CLI + MCP&lt;/td&gt;
&lt;td&gt;Three HTTP calls or MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema source&lt;/td&gt;
&lt;td&gt;SQL, dashboard, or migrations&lt;/td&gt;
&lt;td&gt;Declarative + AI generation&lt;/td&gt;
&lt;td&gt;One JSON document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;SQLite on Cloudflare D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra services&lt;/td&gt;
&lt;td&gt;Many&lt;/td&gt;
&lt;td&gt;Vector DB, edge functions, AI gateway&lt;/td&gt;
&lt;td&gt;Auth and file storage only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access rules&lt;/td&gt;
&lt;td&gt;Row-level security policies&lt;/td&gt;
&lt;td&gt;Per-service config&lt;/td&gt;
&lt;td&gt;In the schema (&lt;code&gt;owner&lt;/code&gt; / &lt;code&gt;authenticated&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Firebase and Supabase are the right call when you want their depth and don't mind that an agent needs hand-holding to drive them. InsForge and Butterbase fit when a generated app needs vector search or serverless functions alongside the database. MoonDB fits when the job is a database with auth and a REST API, and the thing you care about is that an agent can stand it up in one pass and a person can read the whole schema in one screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where MoonDB stops
&lt;/h2&gt;

&lt;p&gt;Be clear-eyed about the trade. MoonDB runs on SQLite through Cloudflare D1, which buys fast reads at the edge and a model with almost nothing to configure. It also sets the ceiling: one writer per database and a 10 GB cap. That holds a to-do app, a CRM for a small team, or a content site with room to spare, but a write-heavy system with thousands of concurrent writers wants a dedicated Postgres. If you're weighing the two, &lt;a href="https://moondb.ai/vs/supabase" rel="noopener noreferrer"&gt;here's how MoonDB compares to Supabase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's also not a vector database. If your app is doing retrieval-augmented generation and needs embeddings stored next to your data, that's a real reason to look at InsForge's vector support or a dedicated vector DB instead. MoonDB stores structured application data, the rows your generated app reads and writes, and leaves RAG to tools built for it.&lt;/p&gt;

&lt;p&gt;For the range most agent-built apps start in, a schema an agent can write and a REST API it can call beats a feature list it can't reach. The &lt;a href="https://moondb.ai/docs" rel="noopener noreferrer"&gt;schema and API reference&lt;/a&gt; covers every field type, and the walkthrough on &lt;a href="https://moondb.ai/blog/generate-rest-api-from-json-schema" rel="noopener noreferrer"&gt;generating a REST API from a JSON schema&lt;/a&gt; goes deeper on the format. If you're comparing agent-facing database tools specifically, the rundown of &lt;a href="https://moondb.ai/blog/database-mcp-server" rel="noopener noreferrer"&gt;database MCP servers&lt;/a&gt; covers that angle.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Is there a backend-as-a-service designed for AI coding agents?
&lt;/h3&gt;

&lt;p&gt;Yes. Several launched recently: InsForge, Base44, Butterbase, and MoonDB among them. They differ from Supabase or Firebase in one concrete way: an agent can provision the whole backend from code, with no dashboard step. You hand over a schema and get a live database, auth, and a REST API back.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes a backend agent-native instead of a normal BaaS?
&lt;/h3&gt;

&lt;p&gt;A normal BaaS assumes a person clicking through a dashboard to make tables and copy keys. An agent-native one is driven entirely by API: the agent sends a declarative schema, reads keys from the JSON response, and gets errors written as instructions it can act on. No human step sits in the middle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can a coding agent like Cursor or Claude Code set up a backend by itself?
&lt;/h3&gt;

&lt;p&gt;Yes. If the backend exposes provisioning over HTTP or an MCP server, the agent can create a project, apply a schema, and start writing data without you touching a console. MoonDB does this in three calls; tools like Base44 and InsForge expose a CLI or MCP server aimed at Claude Code and Cursor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I still need to write SQL or use a dashboard?
&lt;/h3&gt;

&lt;p&gt;No. With a declarative backend you describe tables, fields, and access rules as JSON, and the service compiles that to SQL and runs the migration. The dashboard becomes optional. It's useful for watching what the agent built, but you don't need it to build anything.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://insforge.dev" rel="noopener noreferrer"&gt;InsForge&lt;/a&gt; and its &lt;a href="https://github.com/InsForge/insforge" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;: open-source agent-native backend&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://base44.com/backend" rel="noopener noreferrer"&gt;Base44 backend&lt;/a&gt;: backend with a CLI tuned for Claude Code and Cursor&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://butterbase.ai" rel="noopener noreferrer"&gt;Butterbase&lt;/a&gt;: open-source BaaS with MCP&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.xano.com/blog/best-backends-for-ai-generated-apps/" rel="noopener noreferrer"&gt;Xano: best backends for AI-generated apps&lt;/a&gt;: the no-code vendor's take on the category&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://moondb.ai/docs" rel="noopener noreferrer"&gt;MoonDB documentation&lt;/a&gt;: the schema format and full API reference&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>GLM-5.2 Review: 753B Open-Weight Model That Undercuts GPT-5.5</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Thu, 18 Jun 2026 08:42:42 +0000</pubDate>
      <link>https://dev.to/dmaxdev/glm-52-review-753b-open-weight-model-that-undercuts-gpt-55-37ic</link>
      <guid>https://dev.to/dmaxdev/glm-52-review-753b-open-weight-model-that-undercuts-gpt-55-37ic</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Z.ai's GLM-5.2 is a 753-billion-parameter mixture-of-experts model released under an MIT license on June 16, 2026. It tops GPT-5.5 on SWE-bench Pro (62.1 vs 58.6) and trails Claude Opus 4.8 by about 0.7 points on FrontierSWE (74.4% vs 75.1%). Through OpenRouter, it runs at $1.40 per million input tokens, about 72% cheaper than Claude and GPT. The catch: if you use Z.ai's hosted API instead of self-hosting the weights, your prompts route through servers governed by China's National Intelligence Law.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GLM-5.2 Actually Is
&lt;/h2&gt;

&lt;p&gt;GLM-5.2 comes from Z.ai (formerly Zhipu AI), one of China's largest AI labs and one of the few on the US Bureau of Industry and Security's Entity List. The model launched on June 13 for paying subscribers, then dropped its full open weights on Hugging Face three days later at &lt;a href="https://huggingface.co/zai-org/GLM-5.2" rel="noopener noreferrer"&gt;zai-org/GLM-5.2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The architecture is a mixture-of-experts (MoE) transformer: 753 billion total parameters, but only about 40 billion active per forward pass. That MoE structure is why it can match or beat dense models with far fewer FLOPs per token. It supports a 1-million-token context window (a 5x jump from GLM-5.1's 200K) and can produce up to 128K tokens in a single response.&lt;/p&gt;

&lt;p&gt;I spent the last day running it through OpenRouter on three personal projects: a Flask API refactor, a Go CLI tool with nested subcommands, and a long context window test stuffing 400K tokens of a monorepo into the prompt. Not cherry-picked benchmarks, just the kind of work I do daily with Claude Code and occasionally Codex.&lt;/p&gt;

&lt;p&gt;The first thing I noticed: GLM-5.2 is verbose. It used roughly 43K output tokens per task in Artificial Analysis's benchmark suite, compared to 26K for GLM-5.1. That verbosity inflates your bill if you're paying per token, and it slows down iteration when you're waiting for a response. On OpenRouter, a typical coding task took around 75 seconds, which felt slow compared to Opus 4.8's sub-30-second responses for similar complexity.&lt;/p&gt;

&lt;p&gt;But the code it wrote was clean. A Python web scraper function came back production-ready on the first try: proper error handling, retries with exponential backoff, typed return values. The Go CLI output was similarly solid: correct cobra subcommand nesting, help text, and flag parsing without the usual LLM quirk of inventing nonexistent stdlib packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark Breakdown
&lt;/h2&gt;

&lt;p&gt;The full picture, pulling from &lt;a href="https://artificialanalysis.ai/articles/glm-5-2-is-the-new-leading-open-weights-model-on-the-artificial-analysis-intelligence-index" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt;, BenchLM, and Z.ai's published results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;GLM-5.2&lt;/th&gt;
&lt;th&gt;Claude Opus 4.8&lt;/th&gt;
&lt;th&gt;GPT-5.5&lt;/th&gt;
&lt;th&gt;DeepSeek V4 Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench Pro&lt;/td&gt;
&lt;td&gt;62.1&lt;/td&gt;
&lt;td&gt;69.2&lt;/td&gt;
&lt;td&gt;58.6&lt;/td&gt;
&lt;td&gt;55.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench 2.1&lt;/td&gt;
&lt;td&gt;81.0&lt;/td&gt;
&lt;td&gt;85.0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FrontierSWE&lt;/td&gt;
&lt;td&gt;74.4%&lt;/td&gt;
&lt;td&gt;75.1%&lt;/td&gt;
&lt;td&gt;72.6%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AA Intelligence Index&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDPval-AA v2 (Agentic)&lt;/td&gt;
&lt;td&gt;1524&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1514&lt;/td&gt;
&lt;td&gt;1328&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPQA Diamond&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HLE&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things jump out from this table.&lt;/p&gt;

&lt;p&gt;GLM-5.2 beats GPT-5.5 on every coding benchmark where both have scores (see our &lt;a href="https://www.danilchenko.dev/posts/gpt-5-5-review/" rel="noopener noreferrer"&gt;GPT-5.5 review&lt;/a&gt; for the full breakdown on that model). The SWE-bench Pro gap (62.1 vs 58.6) is real. That's 3.5 points on a benchmark where single-digit gaps separate model generations. On FrontierSWE, GLM-5.2 lands at 74.4%, within 0.7% of Opus 4.8's 75.1% (we covered &lt;a href="https://www.danilchenko.dev/posts/claude-fable-5-review/" rel="noopener noreferrer"&gt;Fable 5's benchmark claims&lt;/a&gt; recently). A 0.7% gap for a model that costs 80% less.&lt;/p&gt;

&lt;p&gt;The agentic score (GDPval-AA v2) stands out: 1524 vs GPT-5.5's 1514. GLM-5.2 is built for long-horizon agent workflows where the model needs to plan across files, run commands, and iterate. Z.ai specifically pitched it as a coding-agent model, and the benchmarks back that framing.&lt;/p&gt;

&lt;p&gt;Where it falls short: hallucination rate sits at 28.1% on the AA-Omniscience Index. That's an improvement over GLM-5.1's 29.4%, but it means roughly one in four factual claims from the model is wrong. For coding tasks this is less of a concern (the compiler catches lies), but for anything research-heavy or fact-dependent, you'll want external verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing: The Real Reason to Pay Attention
&lt;/h2&gt;

&lt;p&gt;Pricing changes how you actually use a model more than benchmarks do.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input (per 1M)&lt;/th&gt;
&lt;th&gt;Output (per 1M)&lt;/th&gt;
&lt;th&gt;Approx. cost/task&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GLM-5.2 (OpenRouter)&lt;/td&gt;
&lt;td&gt;$1.40&lt;/td&gt;
&lt;td&gt;$4.40&lt;/td&gt;
&lt;td&gt;~$0.46&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.8&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$25.00&lt;/td&gt;
&lt;td&gt;~$2.50&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$30.00&lt;/td&gt;
&lt;td&gt;~$3.00&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;$0.44&lt;/td&gt;
&lt;td&gt;$0.87&lt;/td&gt;
&lt;td&gt;~$0.05&lt;/td&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GLM-5.1&lt;/td&gt;
&lt;td&gt;~$1.00&lt;/td&gt;
&lt;td&gt;~$3.00&lt;/td&gt;
&lt;td&gt;~$0.25&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GLM-5.2 isn't the cheapest model in this table. DeepSeek V4 Pro undercuts it by 10x. But DeepSeek V4 Pro also scores significantly lower on every coding benchmark. The interesting position is GLM-5.2 vs the proprietary frontier: you get 95-100% of Claude Opus 4.8's coding performance for roughly 18% of the price.&lt;/p&gt;

&lt;p&gt;The caveat is token consumption. GLM-5.2 burns through 43K output tokens per benchmark task, with 37K of those being internal reasoning tokens. You're billed for the reasoning tokens even though they don't appear in the visible output. That inflates the per-task cost from what the raw price-per-million suggests. At ~$0.46 per coding task, it's still cheap against proprietary options, but it's almost double GLM-5.1's ~$0.25.&lt;/p&gt;

&lt;p&gt;For personal projects and prototyping, the math is obvious: GLM-5.2 through OpenRouter gives you frontier-adjacent coding quality at indie-developer prices (compare this with the &lt;a href="https://www.danilchenko.dev/posts/claude-code-max-vs-pro/" rel="noopener noreferrer"&gt;Claude Code Pro vs Max pricing breakdown&lt;/a&gt;). For production agent pipelines processing thousands of tasks, the token verbosity starts to add up and DeepSeek V4 Pro's cost advantage gets harder to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 1-Million-Token Context Window
&lt;/h2&gt;

&lt;p&gt;GLM-5.1 topped out at 200K tokens. GLM-5.2 jumps to 1 million, matching Claude Opus 4.8 and GPT-5.5's API context window.&lt;/p&gt;

&lt;p&gt;I tested this by feeding it roughly 400K tokens of a monorepo: the entire &lt;code&gt;src/&lt;/code&gt; directory of a mid-size Flask application with about 180 files. I asked it to trace a specific request flow through three microservices, identify where a race condition could happen, and propose a fix.&lt;/p&gt;

&lt;p&gt;It handled the context without obvious degradation. The trace was accurate, it identified the correct database transaction that lacked proper isolation, and the fix was structurally sound. Whether it would hold up at 800K+ tokens I can't say. I didn't have a codebase that large on hand to test with.&lt;/p&gt;

&lt;p&gt;Z.ai specifically designed GLM-5.2's IndexShare sparse-attention mechanism for long agent trajectories. The idea is that coding agents accumulate hundreds of thousands of tokens over a multi-step session: file reads, command outputs, error traces, iterative fixes. A model that degrades at 200K would force the agent to constanly prune context or restart. At 1M, the agent can carry the full project state through a long session without losing earlier context.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use GLM-5.2
&lt;/h2&gt;

&lt;p&gt;Three paths, each with different trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenRouter (quickest start)
&lt;/h3&gt;

&lt;p&gt;The fastest way to try GLM-5.2. Nine providers offer it on OpenRouter, all at roughly $1.40/$4.40 per million tokens. The API is OpenAI-compatible:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://openrouter.ai/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-openrouter-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z-ai/glm-5.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refactor this Flask route to use async SQLAlchemy 2.0 sessions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Z.ai's API (official, but read the data section below)
&lt;/h3&gt;

&lt;p&gt;Z.ai's own API supports OpenAI SDK compatibility and adds a Coding Plan tier that's compatible with Claude Code, Cline, and Cursor. Point your tools at the coding endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.z.ai/api/coding/paas/v4"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_DEFAULT_SONNET_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"glm-5.2[1m]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you GLM-5.2 as a drop-in replacement in Claude Code sessions. I tried this with a small Go project and it worked. Claude Code's agent loop ran normally, though response times were noticeably slower than Anthropic's own endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Hosting (full control, big hardware)
&lt;/h3&gt;

&lt;p&gt;The weights are on Hugging Face under MIT. You can serve them with vLLM or SGLang on your own GPUs. The hardware requirement is steep: at FP8 quantization, you need about 753 GB of VRAM. That's ten H100 80GB GPUs (800 GB total) or a smaller cluster of H200s. At BF16, the requirement doubles to ~1,500 GB.&lt;/p&gt;

&lt;p&gt;Self-hosting makes sense for two audiences: enterprises that can't send code to external APIs for compliance reasons, and AI labs that want to fine-tune the weights for specialized domains. For everyone else, OpenRouter is simpler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example vLLM serving command (8x H100)&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; vllm.entrypoints.openai.api_server &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--model&lt;/span&gt; zai-org/GLM-5.2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--tensor-parallel-size&lt;/span&gt; 8 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--dtype&lt;/span&gt; float8 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--max-model-len&lt;/span&gt; 1048576
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The China Data Question
&lt;/h2&gt;

&lt;p&gt;Z.ai is headquartered in Beijing. The US Bureau of Industry and Security &lt;a href="https://www.federalregister.gov/documents/2025/01/16/2025-00704/" rel="noopener noreferrer"&gt;added Zhipu AI&lt;/a&gt; (Z.ai's former name) to its Entity List in January 2025, citing the company's role in advancing military AI modernization. In late April 2026, US House lawmakers &lt;a href="https://homeland.house.gov/" rel="noopener noreferrer"&gt;opened a formal inquiry&lt;/a&gt; into cybersecurity risks posed by Chinese AI models in critical infrastructure, naming Z.ai alongside DeepSeek, MiniMax, ByteDance, and several others.&lt;/p&gt;

&lt;p&gt;The risk depends entirely on the serving path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you use Z.ai's hosted API&lt;/strong&gt;, your prompts and code route through servers subject to China's National Intelligence Law, which requires Chinese companies to cooperate with government data requests. For regulated industries (healthcare, finance, defense, government contracting), this is a non-starter. For any codebase containing proprietary algorithms, API keys, customer data, or anything you wouldn't post publicly, routing through Z.ai's API is a risk most security teams won't approve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you self-host or use a Western OpenRouter provider&lt;/strong&gt;, the data never touches Z.ai's servers. The MIT license has no phone-home requirements, no telemetry obligations, no usage restrictions. You download the weights, serve them on your own infrastructure, and Z.ai has no visibility into what you're doing. This is the scenario where the open-weights value proposition fully pays off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you use OpenRouter&lt;/strong&gt;, check which provider is serving your request. Most OpenRouter providers for GLM-5.2 route through non-Chinese infrastructure, but the routing can vary. Verify with your specific provider if this matters for your compliance requirements.&lt;/p&gt;

&lt;p&gt;The practical upshot: GLM-5.2's China connection is a non-issue if you self-host, a manageable concern on most OpenRouter providers, and a hard blocker if you'd be sending sensitive code directly to Z.ai. The MIT license exists precisely to decouple the model's capabilities from the company's jurisdiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Use GLM-5.2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Independent developers and small teams who want near-frontier coding quality at $1.40/M input instead of $5.00/M. The cost difference compounds fast when you're running agent loops that process dozens of files.&lt;/li&gt;
&lt;li&gt;Teams already invested in open-weight infrastructure (vLLM clusters, self-hosted inference). GLM-5.2 slots into that stack with no vendor lock-in and no API dependency.&lt;/li&gt;
&lt;li&gt;Long-context use cases where the 1M window matters: codebase-wide refactors, multi-file agents, repository Q&amp;amp;A.&lt;/li&gt;
&lt;li&gt;Anyone who needs to fine-tune a frontier-class coding model for a specific domain. MIT license means no restrictions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bad fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise teams in regulated industries that can't risk any data routing through Chinese infrastructure, unless they have the GPU budget to self-host.&lt;/li&gt;
&lt;li&gt;Latency-sensitive production pipelines. At ~75 seconds per coding task through OpenRouter, GLM-5.2 is sluggish compared to Opus 4.8 or GPT-5.5's APIs.&lt;/li&gt;
&lt;li&gt;Tasks requiring low hallucination rates. The 28.1% factual error rate is acceptable for code (where tests catch mistakes) but rough for content generation, research synthesis, or customer-facing text.&lt;/li&gt;
&lt;li&gt;Teams that need multimodal capabilities. GLM-5.2 is text-only. Z.ai's vision model (GLM-5V-Turbo) exists separately and isn't open-weights.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Is GLM-5.2 better than Claude Opus 4.8 for coding?
&lt;/h3&gt;

&lt;p&gt;Not quite. On SWE-bench Pro, Opus 4.8 scores 69.2 vs GLM-5.2's 62.1, a 7-point gap. On FrontierSWE, it narrows to 75.1% vs 74.4%. Opus 4.8 is faster, less verbose, and has lower hallucination rates. But it costs 3-5x more per token. Whether "better" means "higher score" or "better value" depends on your budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run GLM-5.2 locally?
&lt;/h3&gt;

&lt;p&gt;Technically yes, if you have ~753 GB of VRAM (ten H100 80GB GPUs at FP8). For most developers, local running isn't practical. Cloud hosting through a managed provider or OpenRouter is the realistic path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GLM-5.2 safe to use with proprietary code?
&lt;/h3&gt;

&lt;p&gt;It depends on the serving path. Self-hosted or through a trusted Western provider on OpenRouter — yes, your data doesn't leave your infrastructure. Through Z.ai's own API — your prompts traverse Chinese servers governed by the National Intelligence Law. Most enterprise security teams will block the latter.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does GLM-5.2 compare to DeepSeek V4 Pro?
&lt;/h3&gt;

&lt;p&gt;DeepSeek V4 Pro is ~10x cheaper ($0.44/$0.87 per million tokens) as we detailed in our &lt;a href="https://www.danilchenko.dev/posts/deepseek-v4-pro-review/" rel="noopener noreferrer"&gt;DeepSeek V4 Pro review&lt;/a&gt;, but scores lower on coding benchmarks — 55.4 on SWE-bench Pro vs GLM-5.2's 62.1. DeepSeek wins on cost, GLM-5.2 wins on capability. Both carry similar China-data concerns if used through their respective hosted APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is GLM-5.2 so verbose?
&lt;/h3&gt;

&lt;p&gt;The model produces about 43K output tokens per coding task, with 37K being internal reasoning tokens that get billed but don't appear in the response. Deeper reasoning chains improve accuracy on complex tasks, but they inflate costs and latency. Z.ai hasn't offered a "low verbosity" mode that trades some accuracy for speed.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.z.ai/guides/llm/glm-5.2" rel="noopener noreferrer"&gt;Z.ai GLM-5.2 documentation&lt;/a&gt; — official specs, API reference, and capability overview&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2026/Jun/17/glm-52/" rel="noopener noreferrer"&gt;Simon Willison's analysis of GLM-5.2&lt;/a&gt; — independent benchmarks and observations from the day of release&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://artificialanalysis.ai/articles/glm-5-2-is-the-new-leading-open-weights-model-on-the-artificial-analysis-intelligence-index" rel="noopener noreferrer"&gt;Artificial Analysis: GLM-5.2 is the new leading open-weights model&lt;/a&gt; — Intelligence Index v4.1 scores, GDPval-AA agentic benchmarks, and pricing data&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://venturebeat.com/technology/z-ais-open-weights-glm-5-2-beats-gpt-5-5-on-multiple-long-horizon-coding-benchmarks-for-1-6th-the-cost" rel="noopener noreferrer"&gt;VentureBeat: Z.ai's GLM-5.2 beats GPT-5.5 on multiple benchmarks&lt;/a&gt; — SWE-bench Pro and FrontierSWE comparisons&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openrouter.ai/z-ai/glm-5.2" rel="noopener noreferrer"&gt;OpenRouter GLM-5.2 listing&lt;/a&gt; — live pricing and provider availability&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/zai-org/GLM-5.2" rel="noopener noreferrer"&gt;GLM-5.2 weights on Hugging Face&lt;/a&gt; — MIT-licensed open weights&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;GLM-5.2 is the strongest open-weights coding model available today. At 62.1 on SWE-bench Pro, it beats GPT-5.5 and closes much of the gap to Claude Opus 4.8's 69.2, while costing a fraction of either. The MIT license means you can self-host it, fine-tune it, and deploy it in air-gapped environments.&lt;/p&gt;

&lt;p&gt;The trade-offs are real. It's slow (75 seconds per task on OpenRouter), verbose (burning 43K tokens when 20K might suffice), and the 28.1% hallucination rate means you can't trust it for factual content without verification. The China data question adds a layer: if you can't self-host and your compliance posture rules out Chinese API endpoints, you're limited to OpenRouter's Western providers.&lt;/p&gt;

&lt;p&gt;For non-sensitive coding work where I'm paying out of pocket, GLM-5.2 through OpenRouter at $1.40/M input is the best value in frontier AI right now. It wrote clean Python and Go on the first pass, handled 400K tokens of context without degradation, and saved me about 80% compared to my usual Opus 4.8 API costs. Open-weights coding models have crossed from curiosity to credible daily driver.&lt;/p&gt;

</description>
      <category>glm52</category>
      <category>zai</category>
      <category>openweights</category>
      <category>aicoding</category>
    </item>
    <item>
      <title>LiteLLM Vulnerability: 6 CVEs, a Supply Chain Attack, and the Fixes</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:47:51 +0000</pubDate>
      <link>https://dev.to/dmaxdev/litellm-vulnerability-6-cves-a-supply-chain-attack-and-the-fixes-20cn</link>
      <guid>https://dev.to/dmaxdev/litellm-vulnerability-6-cves-a-supply-chain-attack-and-the-fixes-20cn</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;LiteLLM, the AI gateway proxy used by CrewAI, DSPy, Microsoft GraphRAG, and roughly 97 million monthly PyPI installs, has been hit by six CVEs and a full supply chain compromise since March 2026. The worst chain scores CVSS 10.0 and gives unauthenticated attackers remote code execution on any exposed LiteLLM proxy. CISA added one of the CVEs to its Known Exploited Vulnerabilities catalog on June 8 with a June 22 remediation deadline. If you run LiteLLM, upgrade to v1.83.14+ and rotate every credential the proxy can reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  What LiteLLM Does and Why You're Probably Running It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt; is an open-source Python proxy that gives you a single OpenAI-compatible API in front of 100+ LLM providers: OpenAI, Anthropic, Google, Azure, Bedrock, Cohere, and more. You point your application at the proxy, the proxy routes requests to the right provider, and you get cost tracking, load balancing, rate limiting, and guardrails for free. About 50,300 GitHub stars and 97 million PyPI downloads a month as of June 2026.&lt;/p&gt;

&lt;p&gt;I ran a LiteLLM proxy for three months last year to route Claude and GPT calls from a multi-agent pipeline. The setup took about 15 minutes: a Docker container, a YAML config with my API keys, and a &lt;code&gt;litellm --config config.yaml&lt;/code&gt; command. That simplicity is exactly the problem. LiteLLM sits between your entire AI stack and every model provider you use. Every API key, every prompt, every response flows through it. When the proxy gets owned, the attacker gets everything — and AI tooling &lt;a href="https://www.danilchenko.dev/posts/ai-coding-tools-secret-leaks/" rel="noopener noreferrer"&gt;already leaks secrets at twice the rate of manual code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And in 2026, LiteLLM got owned from every angle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every Vulnerability at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CVE&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;CVSS&lt;/th&gt;
&lt;th&gt;Affected Versions&lt;/th&gt;
&lt;th&gt;Fixed In&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-42271&lt;/td&gt;
&lt;td&gt;Command injection (MCP endpoints)&lt;/td&gt;
&lt;td&gt;8.8 (10.0 chained)&lt;/td&gt;
&lt;td&gt;1.74.2 – 1.83.6&lt;/td&gt;
&lt;td&gt;v1.83.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-48710&lt;/td&gt;
&lt;td&gt;Starlette host header bypass&lt;/td&gt;
&lt;td&gt;Enabler&lt;/td&gt;
&lt;td&gt;Starlette ≤1.0.0&lt;/td&gt;
&lt;td&gt;Starlette 1.0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-42208&lt;/td&gt;
&lt;td&gt;Pre-auth SQL injection&lt;/td&gt;
&lt;td&gt;9.8&lt;/td&gt;
&lt;td&gt;1.81.16 – 1.83.6&lt;/td&gt;
&lt;td&gt;v1.83.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-47101&lt;/td&gt;
&lt;td&gt;Auth bypass (allowed_routes)&lt;/td&gt;
&lt;td&gt;8.8 (9.9 chained)&lt;/td&gt;
&lt;td&gt;&amp;lt;1.83.14&lt;/td&gt;
&lt;td&gt;v1.83.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-47102&lt;/td&gt;
&lt;td&gt;Privilege escalation (user_role)&lt;/td&gt;
&lt;td&gt;Part of 9.9 chain&lt;/td&gt;
&lt;td&gt;&amp;lt;1.83.14&lt;/td&gt;
&lt;td&gt;v1.83.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2026-40217&lt;/td&gt;
&lt;td&gt;Sandbox escape (exec guardrails)&lt;/td&gt;
&lt;td&gt;8.8&lt;/td&gt;
&lt;td&gt;&amp;lt;1.83.14&lt;/td&gt;
&lt;td&gt;v1.83.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus a full supply chain attack on March 24 that injected credential-stealing malware into PyPI packages v1.82.7 and v1.82.8.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE-2026-42271: Remote Code Execution via MCP Test Endpoints
&lt;/h2&gt;

&lt;p&gt;This is the one &lt;a href="https://www.helpnetsecurity.com/2026/06/09/litellm-vulnerability-under-active-attack-cisa-warns-cve-2026-42271/" rel="noopener noreferrer"&gt;CISA flagged on June 8&lt;/a&gt; with a June 22 remediation deadline.&lt;/p&gt;

&lt;p&gt;LiteLLM versions 1.74.2 through 1.83.6 included two MCP server preview endpoints: &lt;code&gt;POST /mcp-rest/test/connection&lt;/code&gt; and &lt;code&gt;POST /mcp-rest/test/tools/list&lt;/code&gt;. These accepted a full server configuration (command, arguments, and environment variables for stdio transport) and LiteLLM spawned the supplied command as a subprocess on the host. Any authenticated user with a standard API key could reach them.&lt;/p&gt;

&lt;p&gt;That alone scores CVSS 8.8. But &lt;a href="https://horizon3.ai/attack-research/vulnerabilities/cve-2026-42271-chained-with-cve-2026-48710/" rel="noopener noreferrer"&gt;Horizon3.ai researchers&lt;/a&gt; chained it with CVE-2026-48710, a host header validation bypass in Starlette versions ≤1.0.0. The Starlette bug lets attackers bypass LiteLLM's authentication layer entirely by sending a malformed &lt;code&gt;Host&lt;/code&gt; header. No API key, no login, no prior access needed.&lt;/p&gt;

&lt;p&gt;The combined chain scores &lt;strong&gt;CVSS 10.0&lt;/strong&gt; and gives any internet-connected attacker unauthenticated RCE on the proxy host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This is what the exploit looks like (simplified for illustration)&lt;/span&gt;
&lt;span class="c"&gt;# The attacker sends a crafted Host header to bypass auth,&lt;/span&gt;
&lt;span class="c"&gt;# then passes arbitrary commands via the MCP test endpoint&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://target-litellm:4000/mcp-rest/test/connection &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: badhost"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"server_name": "evil", "transport": "stdio",
       "command": "/bin/sh", "args": ["-c", "id; cat /proc/self/environ"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Horizon3.ai validated the full chain on June 1. CISA confirmed active exploitation in the wild.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Privilege Escalation Chain: Three CVEs, One Path to Root
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://thehackernews.com/2026/06/litellm-vulnerability-chain-lets-low.html" rel="noopener noreferrer"&gt;Obsidian Security disclosed this chain on June 11&lt;/a&gt;, and it's the scarier find because it starts from a default low-privilege account that every LiteLLM deployment has.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — CVE-2026-47101 (Auth bypass):&lt;/strong&gt; A standard &lt;code&gt;internal_user&lt;/code&gt; calls &lt;code&gt;/key/generate&lt;/code&gt; with &lt;code&gt;allowed_routes: ["/*"]&lt;/code&gt;. LiteLLM validates that the &lt;code&gt;user_id&lt;/code&gt; matches the caller but never checks what routes are being granted. The user now holds a wildcard API key that bypasses all role-based access controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — CVE-2026-47102 (Privilege escalation):&lt;/strong&gt; The wildcard key accesses &lt;code&gt;/user/update&lt;/code&gt;. This endpoint verifies identity but has zero field-level authorization. The attacker sets &lt;code&gt;user_role: "proxy_admin"&lt;/code&gt; on their own account and becomes a full admin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — CVE-2026-40217 (Sandbox escape to RCE):&lt;/strong&gt; Admin-level guardrail endpoints accept user-supplied Python code and compile it with &lt;code&gt;exec()&lt;/code&gt;. The globals dictionary omits &lt;code&gt;__builtins__&lt;/code&gt;, but Python automatically injects the full builtins module when that key is missing. One line gets you shell access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The "sandboxed" guardrail code that actually has full system access
&lt;/span&gt;&lt;span class="n"&gt;__builtins__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__import__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;os&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cat /etc/shadow&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obsidian rates the full chain CVSS 9.9. A server takeover exposes every provider key, every stored credential, every prompt and response passing through the gateway. They demonstrated a "man-in-the-gateway" attack that injected fake tool calls into Claude Code responses, causing it to execute attacker-controlled commands while evading auto-mode safety checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE-2026-42208: SQL Injection in the Auth Layer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.litellm.ai/blog/cve-2026-42208-litellm-proxy-sql-injection" rel="noopener noreferrer"&gt;Bishop Fox found this one&lt;/a&gt;: a pre-authentication SQL injection via the &lt;code&gt;Authorization: Bearer&lt;/code&gt; header. Versions 1.81.16 through 1.83.6 build the token verification query with a Python f-string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The vulnerable code in litellm/proxy/utils.py
&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHERE v.token = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LiteLLM's &lt;code&gt;_hash_token_if_needed()&lt;/code&gt; only hashes values starting with &lt;code&gt;sk-&lt;/code&gt;. Non-prefixed bearer tokens pass straight to the f-string. An attacker can extract the entire &lt;code&gt;LiteLLM_VerificationToken&lt;/code&gt; table — all virtual keys, team bindings, spend records — via timing-based blind injection with &lt;code&gt;pg_sleep()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Default deployments run with database superuser privileges, so the blast radius extends to every table in the database. &lt;a href="https://www.sysdig.com/blog/cve-2026-42208-targeted-sql-injection-against-litellms-authentication-path-discovered-36-hours-following-vulnerability-disclosure" rel="noopener noreferrer"&gt;Sysdig observed&lt;/a&gt; in-the-wild exploitation within 36 hours of the GitHub advisory going public, with attackers targeting &lt;code&gt;litellm_credentials.credential_values&lt;/code&gt; to steal upstream LLM provider keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  The March Supply Chain Attack
&lt;/h2&gt;

&lt;p&gt;On March 24, a threat actor called TeamPCP published two backdoored versions of LiteLLM (v1.82.7 and v1.82.8) to &lt;a href="https://snyk.io/blog/poisoned-security-scanner-backdooring-litellm/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The attack chain started weeks earlier. TeamPCP exploited a &lt;code&gt;pull_request_target&lt;/code&gt; workflow vulnerability in Trivy (the container security scanner), hijacked the &lt;code&gt;aqua-bot&lt;/code&gt; GitHub Actions credentials, and used them to compromise Checkmarx KICS GitHub Actions. When LiteLLM's CI/CD pipeline ran the compromised Trivy scanner during a build, the malicious action extracted LiteLLM's &lt;code&gt;PYPI_PUBLISH&lt;/code&gt; token from the GitHub Actions environment. CI/CD supply chain risks like this are &lt;a href="https://www.danilchenko.dev/posts/github-ai-agents/" rel="noopener noreferrer"&gt;growing across the ecosystem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.herodevs.com/blog-posts/the-litellm-supply-chain-attack-what-happened-why-it-matters-and-what-to-do-next" rel="noopener noreferrer"&gt;malicious payload&lt;/a&gt; installed a &lt;code&gt;.pth&lt;/code&gt; file that fired on every Python interpreter startup — no import required. It harvested environment variables, SSH keys, &lt;code&gt;.env&lt;/code&gt; files, API tokens, AWS/GCP/Azure credentials, Kubernetes configs, and database credentials. Everything was encrypted with AES-256-CBC and RSA-4096, bundled into a tarball, and exfiltrated to &lt;code&gt;models.litellm[.]cloud&lt;/code&gt;. It also deployed persistence via a fake &lt;code&gt;sysmon.service&lt;/code&gt; systemd unit and, in Kubernetes environments, spawned privileged pods on every node.&lt;/p&gt;

&lt;p&gt;PyPI quarantined the packages within about 3 hours. But Microsoft GraphRAG, Google ADK, DSPy, CrewAI, and OpenHands all pulled the malicious version as a transitive dependency during that window. &lt;a href="https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/" rel="noopener noreferrer"&gt;Datadog Security Labs&lt;/a&gt; later linked this to a broader TeamPCP campaign that also hit npm packages and OpenVSX extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Check If You're Affected
&lt;/h2&gt;

&lt;p&gt;Run these checks in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Check your LiteLLM version&lt;/span&gt;
pip show litellm | &lt;span class="nb"&gt;grep &lt;/span&gt;Version
&lt;span class="c"&gt;# or inside Docker:&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;container&amp;gt; pip show litellm | &lt;span class="nb"&gt;grep &lt;/span&gt;Version

&lt;span class="c"&gt;# 2. Check your Starlette version (for the auth bypass chain)&lt;/span&gt;
pip show starlette | &lt;span class="nb"&gt;grep &lt;/span&gt;Version

&lt;span class="c"&gt;# 3. Check if the vulnerable MCP endpoints exist&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost:4000/mcp-rest/test/connection
&lt;span class="c"&gt;# 404 = not vulnerable (endpoint removed). 405 or 200 = vulnerable.&lt;/span&gt;

&lt;span class="c"&gt;# 4. Check for supply chain attack artifacts&lt;/span&gt;
find / &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"litellm_init.pth"&lt;/span&gt; 2&amp;gt;/dev/null
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/.config/sysmon/sysmon.py 2&amp;gt;/dev/null
systemctl status sysmon.service 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your LiteLLM version is below 1.83.14, you're exposed to at least some of these vulnerabilities. If it's between 1.82.7 and 1.82.8 and you installed between March 24 10:39 UTC and ~13:38 UTC, you may have the supply chain backdoor.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Fix Everything
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Upgrade LiteLLM to at least v1.83.14&lt;/strong&gt; (v1.89.1 is the latest as of June 16):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# pip upgrade&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="s2"&gt;"litellm&amp;gt;=1.83.14"&lt;/span&gt;

&lt;span class="c"&gt;# Docker — pull the latest stable image&lt;/span&gt;
docker pull ghcr.io/berriai/litellm:main-latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Upgrade Starlette&lt;/strong&gt; to close the unauthenticated RCE path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; &lt;span class="s2"&gt;"starlette&amp;gt;=1.0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rotate credentials.&lt;/strong&gt; If your proxy was ever internet-accessible on a vulnerable version, treat every secret it could reach as compromised:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rotate these (at minimum):&lt;/span&gt;
&lt;span class="c"&gt;# - LITELLM_MASTER_KEY and LITELLM_SALT_KEY&lt;/span&gt;
&lt;span class="c"&gt;# - DATABASE_URL and database passwords&lt;/span&gt;
&lt;span class="c"&gt;# - Every LLM provider API key (OpenAI, Anthropic, Google, Azure, Bedrock)&lt;/span&gt;
&lt;span class="c"&gt;# - MCP and agent OAuth tokens&lt;/span&gt;
&lt;span class="c"&gt;# - SSH keys and cloud credentials (AWS, GCP, Azure)&lt;/span&gt;

&lt;span class="c"&gt;# Check your logs for signs of exploitation:&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"/mcp-rest/test/(connection|tools/list)"&lt;/span&gt; /var/log/litellm/&lt;span class="k"&gt;*&lt;/span&gt;.log
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"/user/(update|bulk_update).*user_role"&lt;/span&gt; /var/log/litellm/&lt;span class="k"&gt;*&lt;/span&gt;.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you were hit by the supply chain attack, rebuild your containers from scratch. The backdoor persists through restarts via systemd and &lt;code&gt;.pth&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Can't upgrade immediately? These temporary mitigations buy time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Block the vulnerable endpoints at your reverse proxy (nginx example)&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;^/mcp-rest/test/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Block non-standard bearer tokens to mitigate SQL injection&lt;/span&gt;
&lt;span class="c1"&gt;# Only allow tokens matching the sk- prefix pattern&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Went Wrong, and What It Means for Your AI Stack
&lt;/h2&gt;

&lt;p&gt;LiteLLM's security problems aren't unusual for a fast-growing open-source project. The f-string SQL query is a rookie mistake, but it's the kind of mistake that gets shipped when the priority is features, not security audits. The &lt;code&gt;exec()&lt;/code&gt; sandbox with missing builtins is a well-known Python gotcha that's been documented since 2009.&lt;/p&gt;

&lt;p&gt;The supply chain attack is worse. TeamPCP didn't need to find a bug in LiteLLM's code, and &lt;a href="https://www.danilchenko.dev/posts/claude-found-500-zero-days/" rel="noopener noreferrer"&gt;AI-driven vulnerability discovery&lt;/a&gt; means the attack surface is only growing. They compromised the security scanner that LiteLLM's CI pipeline trusted, then used that to steal PyPI publishing credentials. The attack took weeks to set up and targeted LiteLLM specifically because it sits at the chokepoint of thousands of AI deployments.&lt;/p&gt;

&lt;p&gt;If you're running any AI gateway or proxy, the takeaway is straightforward: treat it like you'd treat your database. Don't expose it to the internet without authentication. Don't run it as root. Pin your dependency versions (a &lt;a href="https://www.danilchenko.dev/posts/uv-vs-pip-vs-poetry/" rel="noopener noreferrer"&gt;good package manager&lt;/a&gt; helps), verify checksums on deployment, and have a credential rotation plan that doesn't require a Saturday fire drill.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.crewai.com/en/learn/litellm-removal-guide" rel="noopener noreferrer"&gt;CrewAI published a removal guide&lt;/a&gt; for decoupling from LiteLLM entirely. Whether that's the right move depends on your threat model. LiteLLM has since engaged Veria Labs for a proxy audit, launched a bug bounty ($500–$3,000), and committed to 5-business-day response times on advisories. The project is taking security more seriously. Whether that's enough depends on how many more CVEs are sitting in the other 100,000+ lines of Python.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What is CVE-2026-42271?
&lt;/h3&gt;

&lt;p&gt;CVE-2026-42271 is a command injection vulnerability in LiteLLM's MCP REST test endpoints. Two preview endpoints (&lt;code&gt;/mcp-rest/test/connection&lt;/code&gt; and &lt;code&gt;/mcp-rest/test/tools/list&lt;/code&gt;) accepted full server configurations and spawned the supplied command as a subprocess. When chained with a Starlette host header bypass (CVE-2026-48710), it becomes an unauthenticated remote code execution vulnerability scoring CVSS 10.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  What versions of LiteLLM are affected?
&lt;/h3&gt;

&lt;p&gt;For the RCE and SQL injection vulnerabilities: versions 1.74.2 through 1.83.6. For the privilege escalation chain: all versions before 1.83.14. The supply chain attack only affected PyPI packages v1.82.7 and v1.82.8, published on March 24 and quarantined within 3 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to fix LiteLLM vulnerability?
&lt;/h3&gt;

&lt;p&gt;Upgrade to LiteLLM v1.83.14 or later (v1.89.1 is the latest as of June 2026), upgrade Starlette to v1.0.1+, and rotate every credential the proxy had access to: LLM provider keys, database passwords, the master key, SSH keys, and cloud credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is LiteLLM safe to use in production?
&lt;/h3&gt;

&lt;p&gt;After upgrading to v1.83.14+, the known vulnerabilities are patched. But LiteLLM has accumulated 16 CVEs across 2024–2026, including three Criticals. If you use it in production, run it behind a reverse proxy, restrict network access, disable unused features like Custom Code Guardrails, and monitor the &lt;a href="https://github.com/BerriAI/litellm/security/advisories" rel="noopener noreferrer"&gt;GitHub advisory feed&lt;/a&gt; closely.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happened with the LiteLLM supply chain attack?
&lt;/h3&gt;

&lt;p&gt;On March 24, 2026, a threat actor called TeamPCP published backdoored LiteLLM versions 1.82.7 and 1.82.8 to PyPI. They got there by first compromising Trivy's GitHub Actions, then using that access to steal LiteLLM's PyPI publishing credentials from CI/CD. The malicious packages harvested every credential on the host machine and exfiltrated them to an attacker-controlled server. PyPI quarantined them within about 3 hours, but major frameworks like Microsoft GraphRAG and Google ADK pulled the malicious versions as transitive dependencies during that window.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the LiteLLM exploit chain work?
&lt;/h3&gt;

&lt;p&gt;Multiple chains exist. The worst (CVSS 10.0) combines a command injection in MCP test endpoints with a Starlette host header bypass for unauthenticated RCE. A separate chain (CVSS 9.9) starts from a low-privilege default account: the attacker creates a wildcard API key via an unchecked &lt;code&gt;allowed_routes&lt;/code&gt; field, self-promotes to admin by modifying their own &lt;code&gt;user_role&lt;/code&gt;, then executes arbitrary Python through the Custom Code Guardrail's broken &lt;code&gt;exec()&lt;/code&gt; sandbox.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://horizon3.ai/attack-research/vulnerabilities/cve-2026-42271-chained-with-cve-2026-48710/" rel="noopener noreferrer"&gt;Horizon3.ai — CVE-2026-42271 chained with CVE-2026-48710&lt;/a&gt; — original research on the CVSS 10.0 RCE chain&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://thehackernews.com/2026/06/litellm-vulnerability-chain-lets-low.html" rel="noopener noreferrer"&gt;The Hacker News — LiteLLM Vulnerability Chain Lets Low-Privilege Users Take Over&lt;/a&gt; — coverage of the Obsidian Security CVSS 9.9 privilege escalation disclosure&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.helpnetsecurity.com/2026/06/09/litellm-vulnerability-under-active-attack-cisa-warns-cve-2026-42271/" rel="noopener noreferrer"&gt;Help Net Security — LiteLLM vulnerability under active attack, CISA warns&lt;/a&gt; — CISA KEV announcement&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.litellm.ai/blog/cve-2026-42208-litellm-proxy-sql-injection" rel="noopener noreferrer"&gt;Bishop Fox / LiteLLM — CVE-2026-42208 SQL Injection&lt;/a&gt; — official advisory for the pre-auth SQL injection&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.sysdig.com/blog/cve-2026-42208-targeted-sql-injection-against-litellms-authentication-path-discovered-36-hours-following-vulnerability-disclosure" rel="noopener noreferrer"&gt;Sysdig — CVE-2026-42208 exploitation analysis&lt;/a&gt; — in-the-wild exploitation within 36 hours&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://snyk.io/blog/poisoned-security-scanner-backdooring-litellm/" rel="noopener noreferrer"&gt;Snyk — How a Poisoned Security Scanner Became the Key to Backdooring LiteLLM&lt;/a&gt; — technical breakdown of the supply chain attack&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.herodevs.com/blog-posts/the-litellm-supply-chain-attack-what-happened-why-it-matters-and-what-to-do-next" rel="noopener noreferrer"&gt;HeroDevs — The LiteLLM Supply Chain Attack&lt;/a&gt; — timeline and impact assessment&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://securitylabs.datadoghq.com/articles/litellm-compromised-pypi-teampcp-supply-chain-campaign/" rel="noopener noreferrer"&gt;Datadog Security Labs — LiteLLM compromised PyPI TeamPCP campaign&lt;/a&gt; — broader TeamPCP campaign analysis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.litellm.ai/blog/security-hardening-april-2026" rel="noopener noreferrer"&gt;LiteLLM — Security Hardening April 2026&lt;/a&gt; — official post-incident security improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;LiteLLM got hammered from every direction in 2026: supply chain poisoning, unauthenticated RCE, SQL injection in the auth layer, privilege escalation from default accounts. If you've been running an internet-exposed proxy on anything older than v1.83.7, assume breach and act accordingly. The CISA deadline is June 22 — five days from now.&lt;/p&gt;

</description>
      <category>litellm</category>
      <category>aisecurity</category>
      <category>vulnerability</category>
      <category>python</category>
    </item>
    <item>
      <title>Gemini 3.5 Flash vs Claude Haiku 4.5 vs MAI-Code-1-Flash for Coding</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Wed, 10 Jun 2026 08:35:36 +0000</pubDate>
      <link>https://dev.to/dmaxdev/gemini-35-flash-vs-claude-haiku-45-vs-mai-code-1-flash-for-coding-2mmk</link>
      <guid>https://dev.to/dmaxdev/gemini-35-flash-vs-claude-haiku-45-vs-mai-code-1-flash-for-coding-2mmk</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Three flash-tier coding models are competing for your API budget right now: Google's Gemini 3.5 Flash (May 19, 2026), Anthropic's Claude Haiku 4.5 (the reigning budget pick since October 2025), and Microsoft's MAI-Code-1-Flash (June 2, 2026). Haiku wins on output cost at $5/M tokens and structured output reliability. Gemini 3.5 Flash leads on agentic benchmarks (76.2% Terminal-Bench 2.1) and offers a 1M-token context window. MAI-Code-1-Flash beats both on SWE-Bench Pro by 16 points at 51.2%, but you can only use it inside GitHub Copilot. Pick based on where you actually build: Copilot users get MAI-Code-1 for free, API builders choose between Haiku's cost and Flash's context, and anyone running agent loops with tool calls should benchmark Flash first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Flash Models, Three Different Bets
&lt;/h2&gt;

&lt;p&gt;I've spent the last three weeks routing coding tasks through all three of these models: code reviews in Copilot, agent loops via API, and batch refactors across a 40-file Python project. The experience taught me something the benchmark tables don't show: each model was built with a different definition of "coding" in mind.&lt;/p&gt;

&lt;p&gt;Google optimized &lt;a href="https://deepmind.google/models/model-cards/gemini-3-5-flash/" rel="noopener noreferrer"&gt;Gemini 3.5 Flash&lt;/a&gt; for agents that run in terminals, call tools, and iterate. Anthropic built &lt;a href="https://www.anthropic.com/news/claude-haiku-4-5" rel="noopener noreferrer"&gt;Haiku 4.5&lt;/a&gt; for developers who need a cheap, fast model that follows instructions precisely and returns clean JSON. Microsoft trained &lt;a href="https://microsoft.ai/news/introducingmai-code-1-flash/" rel="noopener noreferrer"&gt;MAI-Code-1-Flash&lt;/a&gt; end-to-end inside the &lt;a href="https://www.danilchenko.dev/posts/copilot-ai-credits/" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; harness, so it knows how VS Code works, what diffs look like, and how to stay concise in inline completions.&lt;/p&gt;

&lt;p&gt;Each model answers a different version of the same question: "What should a small coding model be good at?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmark Comparison
&lt;/h2&gt;

&lt;p&gt;Benchmarks don't capture everything, but they're measurable. Start here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark&lt;/th&gt;
&lt;th&gt;Gemini 3.5 Flash&lt;/th&gt;
&lt;th&gt;Claude Haiku 4.5&lt;/th&gt;
&lt;th&gt;MAI-Code-1-Flash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SWE-Bench Verified&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;73.3% (Anthropic) / 66.6% (Microsoft's eval)&lt;/td&gt;
&lt;td&gt;71.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWE-Bench Pro&lt;/td&gt;
&lt;td&gt;55.1%&lt;/td&gt;
&lt;td&gt;35.2%&lt;/td&gt;
&lt;td&gt;51.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench 2.1&lt;/td&gt;
&lt;td&gt;76.2%&lt;/td&gt;
&lt;td&gt;41.6%*&lt;/td&gt;
&lt;td&gt;54.8%*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP Atlas (tool use)&lt;/td&gt;
&lt;td&gt;83.6%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IF-Bench (instruction following)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;+28.9 pts over Haiku&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Terminal-Bench numbers for Haiku and MAI-Code-1-Flash are from Microsoft's evaluation (Terminal-Bench 2, not 2.1). Direct comparison to Flash's 76.2% on v2.1 should be taken with a grain of salt.&lt;/p&gt;

&lt;p&gt;A few things jump out from this table.&lt;/p&gt;

&lt;p&gt;The SWE-Bench Verified discrepancy for Haiku is real and worth flagging. &lt;a href="https://www.anthropic.com/news/claude-haiku-4-5" rel="noopener noreferrer"&gt;Anthropic reports 73.3%&lt;/a&gt;, &lt;a href="https://x.com/MicrosoftAI/status/2061887514269438302" rel="noopener noreferrer"&gt;Microsoft reports 66.6%&lt;/a&gt; when benchmarking against MAI-Code-1-Flash. The difference probably comes down to evaluation setup: system prompts, tool availability, and retry policies all shift SWE-Bench scores. I wouldn't treat either number as gospel. The relative ranking across SWE-Bench Pro, where the gap is enormous (51.2% vs 35.2%), is more informative.&lt;/p&gt;

&lt;p&gt;Gemini 3.5 Flash dominates the agentic benchmarks. Terminal-Bench 2.1 simulates a real engineer working in a sandboxed terminal with a 5-hour timeout — planning, iterating, and coordinating across tools. Flash's 76.2% puts it above &lt;a href="https://www.danilchenko.dev/posts/gpt-claude-gemini-coding/" rel="noopener noreferrer"&gt;Gemini 3.1 Pro and close to GPT-5.5&lt;/a&gt; territory. If your coding model runs inside an agent loop with tool calls, this number is the one that predicts real-world behavior.&lt;/p&gt;

&lt;p&gt;MAI-Code-1-Flash's instruction following is the other number worth reading. The +28.9 point lead over Haiku on IF-Bench shows Microsoft's harness-native training paid off. The model knows how to handle structured requests ("edit only lines 14-22", "don't touch the imports", "return a unified diff") because it learned from Copilot's actual production request patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing: What You'll Actually Pay
&lt;/h2&gt;

&lt;p&gt;Flash models live or die on cost. If price didn't matter, you'd use &lt;a href="https://www.danilchenko.dev/posts/gpt-claude-gemini-coding/" rel="noopener noreferrer"&gt;Claude Opus 4.7 or GPT-5.5&lt;/a&gt;. Per-million-token pricing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gemini 3.5 Flash&lt;/th&gt;
&lt;th&gt;Claude Haiku 4.5&lt;/th&gt;
&lt;th&gt;MAI-Code-1-Flash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input (per 1M tokens)&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output (per 1M tokens)&lt;/td&gt;
&lt;td&gt;$9.00&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$4.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cached input (per 1M)&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;td&gt;$0.075&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context window&lt;/td&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;200,000&lt;/td&gt;
&lt;td&gt;Not disclosed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output limit&lt;/td&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;64,000&lt;/td&gt;
&lt;td&gt;Not disclosed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;API, Google AI Studio&lt;/td&gt;
&lt;td&gt;API, Anthropic Console&lt;/td&gt;
&lt;td&gt;GitHub Copilot only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The output price gap is the one that bites you. Code generation is output-heavy. A typical agent loop generating a 200-line file produces 8-12K output tokens per turn. At those volumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Haiku&lt;/strong&gt;: $0.05-0.06 per turn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAI-Code-1-Flash&lt;/strong&gt;: $0.036-0.054 per turn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 3.5 Flash&lt;/strong&gt;: $0.072-0.108 per turn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Across a full day of heavy coding (say 200 agent turns), that's $10 for Haiku, $8 for MAI-Code-1, and $18 for Gemini Flash. The gap compounds fast.&lt;/p&gt;

&lt;p&gt;But MAI-Code-1-Flash has a catch: those prices are from GitHub's model picker listing. You can't hit the model through a standalone API endpoint. It only runs inside Copilot. If you're building your own agent framework, your choices are Haiku or Flash.&lt;/p&gt;

&lt;p&gt;And Flash has its own cost lever: cached input at $0.15/M. If your agent loop sends the same system prompt and codebase context on every turn (most do), you're paying 90% less for input after the first call. That cached-input discount often offsets the higher output price for long-running agent sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Efficiency: MAI-Code-1-Flash's 60% Claim
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://microsoft.ai/news/introducingmai-code-1-flash/" rel="noopener noreferrer"&gt;Microsoft claims&lt;/a&gt; MAI-Code-1-Flash "solves harder problems with up to 60% fewer tokens" on SWE-Bench Verified. That's a big number. The model costs less per token AND uses fewer tokens to reach the same solution.&lt;/p&gt;

&lt;p&gt;I tested this informally on my own codebase. I asked all three models to add input validation to a FastAPI endpoint. Same prompt, same context, same expected output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Prompt: Add Pydantic validation to this endpoint.
# Validate: name (str, 2-50 chars), email (valid format), age (18-120)
&lt;/span&gt;
&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# ... existing logic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Haiku 4.5&lt;/strong&gt;: 847 output tokens. Clean solution, used &lt;code&gt;EmailStr&lt;/code&gt; from Pydantic, added a proper error handler. Correct on first try.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 3.5 Flash&lt;/strong&gt;: 1,241 output tokens. Added validation plus a lengthy explanation of each field constraint, a usage example, and a curl command. The code was correct but I didn't ask for the tutorial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAI-Code-1-Flash&lt;/strong&gt; (via Copilot): 512 output tokens. Returned only the modified function with a minimal Pydantic model. No explanation, no example. Correct and concise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This single test isn't a benchmark. But it matches the pattern Microsoft describes: MAI-Code-1-Flash learned from Copilot interactions where conciseness is the default. It doesn't explain unless you ask.&lt;/p&gt;

&lt;p&gt;Flash's verbosity isn't always a downside. If you're prototyping and want the model to think aloud, that extra context helps. But for batch operations and agent loops where you're parsing structured output, fewer tokens means faster iteration and lower cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Windows: The 1M Advantage
&lt;/h2&gt;

&lt;p&gt;This is where Gemini 3.5 Flash separates itself from the other two.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Context Window&lt;/th&gt;
&lt;th&gt;Output Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.5 Flash&lt;/td&gt;
&lt;td&gt;1,000,000 tokens&lt;/td&gt;
&lt;td&gt;65,536 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;200,000 tokens&lt;/td&gt;
&lt;td&gt;64,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAI-Code-1-Flash&lt;/td&gt;
&lt;td&gt;Not disclosed&lt;/td&gt;
&lt;td&gt;Not disclosed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A million-token context window means you can feed Flash an entire mid-sized codebase (50-80 files of typical Python or TypeScript) in a single prompt. Haiku's 200K is generous by historical standards but won't hold the same volume. If you're doing codebase-wide analysis, architecture reviews, or cross-file refacotrs, Flash is the only flash-tier option that won't force you to chunk.&lt;/p&gt;

&lt;p&gt;Both Haiku and Flash now support large output limits (64K and 65K respectively), so you won't hit output ceilings on most tasks. I've pushed both models through full-file rewrites of 300-line modules without truncation. The context &lt;em&gt;input&lt;/em&gt; limit is the real differentiator: Flash's 1M lets you include far more codebase context per request.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://www.danilchenko.dev/posts/copilot-ai-credits/" rel="noopener noreferrer"&gt;Copilot&lt;/a&gt; workflows where MAI-Code-1-Flash operates, the context window is less of an issue. Copilot manages the context for you, feeding relevant files and recent edits. You don't directly control the prompt size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Each Model Wins
&lt;/h2&gt;

&lt;p&gt;After three weeks of testing, I'd route tasks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini 3.5 Flash — agent loops and long-context analysis&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Flash is the model I'd pick for any workflow that involves iterating with tools. Write a failing test, run it, read the error, fix the code, run again. Flash handles that loop better than the other two. Its Terminal-Bench scores reflect a model that was built for multi-turn tool coordination, not just static code generation. The 1M context window makes it the default choice for "analyze this whole codebase" tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Haiku 4.5 — structured output, code review, and high-volume batch work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Haiku returns the cleanest structured output of the three. If you're calling the model 10,000 times a day for code review comments, PR summaries, or JSON-formatted analysis, Haiku's combination of reliable instruction following and the cheapest output tokens makes it the rational choice. It's also the model I trust most for &lt;a href="https://www.danilchenko.dev/posts/opencode-vs-claude-code/" rel="noopener noreferrer"&gt;diff generation and structured editing tasks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MAI-Code-1-Flash — inline completions and Copilot-native workflows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you live in VS Code and use &lt;a href="https://www.danilchenko.dev/posts/copilot-ai-credits/" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt;, MAI-Code-1-Flash is the model that feels most native. It knows the environment: when to suggest a single line vs. a full function, it handles diffs cleanly, and it stays concise. The 60% token efficiency claim holds up in practice for the type of tasks Copilot handles — inline edits, small refactors, and completion suggestions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Availability and Integration
&lt;/h2&gt;

&lt;p&gt;This is the practical differentiator most comparisons skip. It doesn't matter how good a model is if you can't access it from your stack.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gemini 3.5 Flash&lt;/th&gt;
&lt;th&gt;Claude Haiku 4.5&lt;/th&gt;
&lt;th&gt;MAI-Code-1-Flash&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standalone API&lt;/td&gt;
&lt;td&gt;Yes (Gemini API)&lt;/td&gt;
&lt;td&gt;Yes (Anthropic API)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google AI Studio&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code (direct)&lt;/td&gt;
&lt;td&gt;Via extension/API&lt;/td&gt;
&lt;td&gt;Via extension/API&lt;/td&gt;
&lt;td&gt;Built-in via Copilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosting&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MAI-Code-1-Flash's Copilot lock-in is the biggest caveat in this comparison. Microsoft has signaled plans for Azure Foundry and third-party provider access, but as of early June 2026 the model is still rolling out primarily through Copilot. If you're building custom agents, pipelines, or CI/CD integrations, MAI-Code-1-Flash isn't an option today.&lt;/p&gt;

&lt;p&gt;For API access, both Flash and Haiku work through OpenRouter too, so you can swap between them without changing your client code. If you're also evaluating open-source alternatives, &lt;a href="https://www.danilchenko.dev/posts/deepseek-v4-pro-review/" rel="noopener noreferrer"&gt;DeepSeek V4 Pro&lt;/a&gt; punches above its weight at a fraction of the cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Quick Start
&lt;/h2&gt;

&lt;p&gt;Calling each model from Python:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini 3.5 Flash:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-3.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refactor this function to use list comprehension instead of a for loop:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;def filter_active(users):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    result = []&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    for u in users:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;        if u.is_active:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            result.append(u.name)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude Haiku 4.5:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refactor this function to use list comprehension instead of a for loop:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;def filter_active(users):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    result = []&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    for u in users:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;        if u.is_active:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;            result.append(u.name)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filter_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;MAI-Code-1-Flash&lt;/strong&gt; (via Copilot — no standalone API):&lt;/p&gt;

&lt;p&gt;Open the file in VS Code with GitHub Copilot enabled, select the function, and run Copilot Chat with the prompt. If you're working with Gemini's broader toolchain, the &lt;a href="https://www.danilchenko.dev/posts/gemini-cli-tutorial/" rel="noopener noreferrer"&gt;Gemini CLI tutorial&lt;/a&gt; covers the terminal setup. MAI-Code-1-Flash activates through the model picker when available, or via the "auto" selector that routes to it for coding tasks.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Which is better for coding, Gemini 3.5 Flash or Claude Haiku 4.5?
&lt;/h3&gt;

&lt;p&gt;It depends on the task shape. Gemini 3.5 Flash outperforms on agentic coding — multi-step workflows with tool calls and terminal interaction (76.2% Terminal-Bench 2.1). Claude Haiku 4.5 leads on SWE-Bench Verified (73.3%) and costs 44% less on output tokens. For high-volume batch code tasks, Haiku's price wins. For agent loops, Flash's quality wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does Gemini 3.5 Flash cost compared to Claude Haiku 4.5?
&lt;/h3&gt;

&lt;p&gt;Gemini 3.5 Flash charges $1.50/$9.00 per million input/output tokens. Claude Haiku 4.5 charges $1.00/$5.00. Flash is 50% more expensive on input and 80% more on output. But Flash's cached input rate ($0.15/M) can offset the difference in long agent sessions where you repeat the same context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is MAI-Code-1-Flash better than Claude Haiku 4.5?
&lt;/h3&gt;

&lt;p&gt;On Microsoft's own benchmarks, yes — particularly SWE-Bench Pro (51.2% vs 35.2%) and instruction following (+28.9 points). But there's a benchmark discrepancy: Microsoft reports Haiku's SWE-Bench Verified score as 66.6%, while Anthropic reports 73.3%. And MAI-Code-1-Flash is only available inside GitHub Copilot, not via API. If you need a standalone API model, Haiku is your pick regardless of benchmark numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which flash model is cheapest for coding?
&lt;/h3&gt;

&lt;p&gt;MAI-Code-1-Flash has the lowest per-token cost ($0.75/$4.50 per million) AND uses up to 60% fewer tokens per task. But it's locked to GitHub Copilot. For API users, Claude Haiku 4.5 at $1.00/$5.00 is the cheapest option. Gemini 3.5 Flash is the most expensive at $1.50/$9.00, though its prompt caching drops repeated-context costs to $0.15/M input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use MAI-Code-1-Flash outside of GitHub Copilot?
&lt;/h3&gt;

&lt;p&gt;Not currently. MAI-Code-1-Flash is rolling out exclusively through GitHub Copilot's model picker in VS Code. Microsoft hasn't announced an Azure AI endpoint or standalone API. If you need API access for custom agents or CI/CD, you're limited to Gemini 3.5 Flash and Claude Haiku 4.5.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://deepmind.google/models/model-cards/gemini-3-5-flash/" rel="noopener noreferrer"&gt;Gemini 3.5 Flash Model Card — Google DeepMind&lt;/a&gt; — official specs, benchmark numbers, and pricing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://microsoft.ai/news/introducingmai-code-1-flash/" rel="noopener noreferrer"&gt;Introducing MAI-Code-1-Flash — Microsoft AI&lt;/a&gt; — official announcement with SWE-Bench and IF-Bench comparisons&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/news/claude-haiku-4-5" rel="noopener noreferrer"&gt;Introducing Claude Haiku 4.5 — Anthropic&lt;/a&gt; — official announcement with SWE-Bench Verified score&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://x.com/MicrosoftAI/status/2061887514269438302" rel="noopener noreferrer"&gt;Microsoft AI on X: benchmark comparison tweet&lt;/a&gt; — the SWE-Bench Verified 71.6 vs 66.6 numbers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://evolink.ai/blog/gemini-3-5-flash-vs-claude-haiku-4-5" rel="noopener noreferrer"&gt;Gemini 3.5 Flash vs Claude Haiku 4.5: Pricing &amp;amp; Production Fit — Evolink&lt;/a&gt; — independent pricing and performance comparison&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;The flash-tier coding model race in mid-2026 isn't about finding one winner. It's about matching models to workflows.&lt;/p&gt;

&lt;p&gt;If you build custom agents and need a model that handles tool calls and long context, Gemini 3.5 Flash is the leader. If you need the cheapest reliable model for structured output at scale, Claude Haiku 4.5 is the safe bet. And if you code in VS Code with Copilot all day, MAI-Code-1-Flash is quietly the best inline coding model available — you just can't take it anywhere else.&lt;/p&gt;

&lt;p&gt;The lock-in question matters more than the benchmarks. Google and Anthropic sell tokens; Microsoft sells a workflow. Right now, MAI-Code-1-Flash's Copilot exclusivity makes it a non-starter for anyone building outside that stack. If Microsoft opens API access — and the &lt;a href="https://www.danilchenko.dev/posts/copilot-ai-credits/" rel="noopener noreferrer"&gt;GitHub Copilot AI credits system&lt;/a&gt; suggests they're heading that direction — the pricing math changes for everyone.&lt;/p&gt;

</description>
      <category>gemini35flash</category>
      <category>claudehaiku</category>
      <category>maicode1flash</category>
      <category>aicoding</category>
    </item>
    <item>
      <title>Agent Client Protocol (ACP): Connect Any AI Agent to Any Editor</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Thu, 04 Jun 2026 08:41:00 +0000</pubDate>
      <link>https://dev.to/dmaxdev/agent-client-protocol-acp-connect-any-ai-agent-to-any-editor-2p0m</link>
      <guid>https://dev.to/dmaxdev/agent-client-protocol-acp-connect-any-ai-agent-to-any-editor-2p0m</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The Agent Client Protocol (ACP) is a JSON-RPC 2.0 standard that lets any AI coding agent run inside any supporting editor: JetBrains, Zed, Neovim, Emacs. Think LSP, but for AI agents instead of language servers. The registry already lists 50 agents (Claude Code, Gemini CLI, Codex, GitHub Copilot, Goose, and more). This tutorial walks through real setup in JetBrains and Zed, custom agent config, and what ACP gets right versus where it still needs work.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Weekend ACP Setup Across Three Editors
&lt;/h2&gt;

&lt;p&gt;I use JetBrains for Go and Python, Zed for quick edits, and Neovim when I'm SSH'd into a server. Until a few months ago, that meant three completely separate AI setups: Copilot in JetBrains, Zed's built-in assistant, and nothing in Neovim because I couldn't be bothered to configure yet another integration.&lt;/p&gt;

&lt;p&gt;Then I set up ACP in all three editors over a weekend. By Monday, I had Claude Code, Gemini CLI, and Codex available in every editor through the same protocol. I could switch between agents mid-session depending on the task. Claude for architecture reasoning (especially with &lt;a href="https://www.danilchenko.dev/posts/claude-code-subagents/" rel="noopener noreferrer"&gt;subagents&lt;/a&gt;), Gemini for quick multi-file edits, Codex for throwaway scripts. The configuration files are nearly identical across editors.&lt;/p&gt;

&lt;p&gt;That weekend convinced me ACP is the first protocol I've used that reduced complexity instead of adding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Agent Client Protocol?
&lt;/h2&gt;

&lt;p&gt;Every AI coding tool used to require its own editor plugin. Cursor baked its agent into a VS Code fork. GitHub Copilot needed the Copilot extension. Claude Code ran only in the terminal. If you wanted to use three agents, you needed three separate integrations. And if your editor of choice didn't have a plugin for your preferred agent, you were out of luck.&lt;/p&gt;

&lt;p&gt;ACP fixes this with a simple idea borrowed from &lt;a href="https://microsoft.github.io/language-server-protocol/" rel="noopener noreferrer"&gt;LSP&lt;/a&gt;: define a standard wire protocol between editors and agents, so any agent can plug into any editor.&lt;/p&gt;

&lt;p&gt;The protocol works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The editor (client)&lt;/strong&gt; spawns the agent as a subprocess&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent&lt;/strong&gt; communicates back over &lt;strong&gt;JSON-RPC 2.0 on stdin/stdout&lt;/strong&gt; (local agents) or HTTP/WebSocket (remote agents, still in development)&lt;/li&gt;
&lt;li&gt;Work organizes into &lt;strong&gt;sessions&lt;/strong&gt; (a conversation with shared context) and &lt;strong&gt;turns&lt;/strong&gt; (individual prompt-response cycles)&lt;/li&gt;
&lt;li&gt;The agent can request file access, terminal execution, and tool calls, all through the protocol&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://zed.dev/acp" rel="noopener noreferrer"&gt;Zed Industries&lt;/a&gt; created ACP in August 2025. &lt;a href="https://www.jetbrains.com/acp/" rel="noopener noreferrer"&gt;JetBrains&lt;/a&gt; joined shortly after. By January 2026, they co-launched the &lt;a href="https://agentclientprotocol.com/registry" rel="noopener noreferrer"&gt;ACP Registry&lt;/a&gt;, a directory of compatible agents built into both editors. As of June 2026, &lt;a href="https://github.com/agentclientprotocol/registry" rel="noopener noreferrer"&gt;50 agents&lt;/a&gt; and a growing list of editors implement the spec. Zed and JetBrains have native support; Neovim, Emacs, and VS Code have community plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  ACP vs MCP
&lt;/h2&gt;

&lt;p&gt;If you've worked with the &lt;a href="https://www.danilchenko.dev/posts/fastmcp-mcp-server/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;, you might wonder why we need another protocol. The confusion is understandable because both involve AI agents and JSON-RPC. But they sit on different sides of the agent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;ACP&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Editor ↔ Agent&lt;/td&gt;
&lt;td&gt;Agent ↔ Tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code editor (JetBrains, Zed, Neovim)&lt;/td&gt;
&lt;td&gt;AI application (Claude, GPT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI coding agent&lt;/td&gt;
&lt;td&gt;Tool/data provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How an editor drives an agent&lt;/td&gt;
&lt;td&gt;How an agent calls external tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JSON-RPC over stdio or HTTP&lt;/td&gt;
&lt;td&gt;JSON-RPC over stdio or SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Created by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zed Industries (Aug 2025)&lt;/td&gt;
&lt;td&gt;Anthropic (Nov 2024)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;MCP gives agents access to tools: databases, APIs, file systems, browser automation. ACP gives agents access to editors: file trees, terminals, diff views, user prompts.&lt;/p&gt;

&lt;p&gt;The two protocols stack rather than compete. A typical setup looks like this: Zed (ACP client) launches Claude Code (ACP agent), which then connects to an MCP server for database access. ACP handles the editor-to-agent communication; MCP handles the agent-to-tool communication.&lt;/p&gt;

&lt;p&gt;If you've already built &lt;a href="https://www.danilchenko.dev/posts/chrome-devtools-mcp/" rel="noopener noreferrer"&gt;MCP servers&lt;/a&gt; for your workflow, those still work under ACP. The agent sits in the middle and bridges both protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ACP Registry: 50 Agents
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://agentclientprotocol.com/registry" rel="noopener noreferrer"&gt;ACP Registry&lt;/a&gt; is a curated directory that editors query directly. When you open JetBrains or Zed and browse available agents, you're reading from this registry. It auto-updates hourly from npm, PyPI, and GitHub Releases.&lt;/p&gt;

&lt;p&gt;Here are some agents worth knowing about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Agent&lt;/td&gt;
&lt;td&gt;0.40.0&lt;/td&gt;
&lt;td&gt;Anthropic's Claude, wrapped for ACP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;0.45.0&lt;/td&gt;
&lt;td&gt;Google's official Gemini terminal agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI&lt;/td&gt;
&lt;td&gt;0.15.0&lt;/td&gt;
&lt;td&gt;OpenAI's coding assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;1.0.59&lt;/td&gt;
&lt;td&gt;GitHub's pair programmer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;2026.05.28&lt;/td&gt;
&lt;td&gt;Cursor's integrated agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goose&lt;/td&gt;
&lt;td&gt;1.37.0&lt;/td&gt;
&lt;td&gt;Open source, extensible, local-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kiro&lt;/td&gt;
&lt;td&gt;7.3.21&lt;/td&gt;
&lt;td&gt;AWS-backed coding agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Junie&lt;/td&gt;
&lt;td&gt;1831.35.0&lt;/td&gt;
&lt;td&gt;JetBrains' own AI agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;1.15.13&lt;/td&gt;
&lt;td&gt;Open source agent by Anomaly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok Build&lt;/td&gt;
&lt;td&gt;0.2.11&lt;/td&gt;
&lt;td&gt;xAI's coding agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral Vibe&lt;/td&gt;
&lt;td&gt;2.13.0&lt;/td&gt;
&lt;td&gt;Mistral's open source assistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen Code&lt;/td&gt;
&lt;td&gt;0.17.1&lt;/td&gt;
&lt;td&gt;Alibaba's Qwen coding agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full list has 50 entries. Most are installable in one click from JetBrains or Zed. Custom agents that aren't in the registry can be added manually through config files (covered below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Claude Code via ACP in JetBrains
&lt;/h2&gt;

&lt;p&gt;This is the setup I use daily. JetBrains ships native ACP support across all their IDEs: IntelliJ IDEA, PyCharm, WebStorm, GoLand, the lot. No subscription required for ACP itself (the agents may need their own API keys).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Open the agent panel
&lt;/h3&gt;

&lt;p&gt;Open the &lt;strong&gt;AI Chat&lt;/strong&gt; tool window (usually on the right sidebar). Click the dropdown at the top, which shows your current agent/model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Install from registry
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;"Install From ACP Registry"&lt;/strong&gt; in the dropdown. Scroll to &lt;strong&gt;Claude Agent&lt;/strong&gt; and select it.&lt;/p&gt;

&lt;p&gt;The IDE downloads the agent binary and creates a config entry automatically. You'll be prompted for your Anthropic API key. Paste it in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Verify
&lt;/h3&gt;

&lt;p&gt;Back in the AI Chat panel, select "Claude Agent" from the dropdown. Type a message. If you see Claude responding with code awareness (file context, project structure), the agent is connected and reading your workspace through ACP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure MCP servers (optional)
&lt;/h3&gt;

&lt;p&gt;If you already use MCP servers (a &lt;a href="https://www.danilchenko.dev/posts/chrome-devtools-mcp/" rel="noopener noreferrer"&gt;Chrome DevTools MCP server&lt;/a&gt; or a &lt;a href="https://www.danilchenko.dev/posts/fastmcp-mcp-server/" rel="noopener noreferrer"&gt;FastMCP server&lt;/a&gt;, for example), you can wire them into the ACP agent. Edit &lt;code&gt;~/.jetbrains/acp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_mcp_settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"use_custom_mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"use_idea_mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Claude Agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/claude-acp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"acp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ANTHROPIC_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;use_custom_mcp: true&lt;/code&gt; flag tells the agent to use your MCP config instead of the IDE's built-in tool servers. Restart the IDE after editing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Gemini CLI via ACP in Zed
&lt;/h2&gt;

&lt;p&gt;Zed was the first editor to support ACP (they created the protocol), so the integration is smooth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Open the agent panel
&lt;/h3&gt;

&lt;p&gt;Press &lt;code&gt;Cmd+?&lt;/code&gt; (macOS) or &lt;code&gt;Ctrl+?&lt;/code&gt; (Linux/Windows). This opens the agent panel on the right side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Gemini CLI
&lt;/h3&gt;

&lt;p&gt;Click the &lt;strong&gt;+&lt;/strong&gt; button in the top-right corner. Select &lt;strong&gt;Gemini CLI&lt;/strong&gt; from the registry list.&lt;/p&gt;

&lt;p&gt;You'll be asked for your Google AI API key on first use. Paste it, and the agent starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Use it
&lt;/h3&gt;

&lt;p&gt;Gemini CLI now has full access to your project through ACP. File reading, terminal execution, and diff generation all go through the protocol. If you haven't used Gemini CLI before, our &lt;a href="https://www.danilchenko.dev/posts/gemini-cli-tutorial/" rel="noopener noreferrer"&gt;Gemini CLI tutorial&lt;/a&gt; covers the initial setup and a real Python project walkthrough. You can keep multiple agents active simultaneously: select a different agent from the panel's dropdown to switch, or open a second agent panel for side-by-side comparisons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom agent config in Zed
&lt;/h3&gt;

&lt;p&gt;If you want to customize the agent's environment or add an agent that isn't in the registry, edit &lt;code&gt;~/.config/zed/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Gemini CLI"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"registry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"GOOGLE_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIza..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"My Custom Agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/my-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"acp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MY_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registry agents use &lt;code&gt;"type": "registry"&lt;/code&gt; and the registry name as the key. Custom agents use &lt;code&gt;"type": "custom"&lt;/code&gt; with a command path. Both communicate over stdio using the same ACP JSON-RPC protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Agent Configuration
&lt;/h2&gt;

&lt;p&gt;Both JetBrains and Zed support custom agents that aren't in the registry. The config format is nearly identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  JetBrains: &lt;code&gt;~/.jetbrains/acp.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OpenCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/opencode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"acp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"OPENAI_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Zed: &lt;code&gt;~/.config/zed/settings.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"OpenCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bin/opencode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"acp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Neovim: community plugins
&lt;/h3&gt;

&lt;p&gt;Neovim support comes through community plugins like &lt;a href="https://github.com/olimorris/codecompanion.nvim" rel="noopener noreferrer"&gt;CodeCompanion.nvim&lt;/a&gt; and &lt;a href="https://github.com/yetone/avante.nvim" rel="noopener noreferrer"&gt;avante.nvim&lt;/a&gt;. The config follows the same pattern: a command path, args, and environment variables. Neovim spawns the agent as a subprocess and pipes JSON-RPC over stdio, same as the other editors.&lt;/p&gt;

&lt;p&gt;The key thing across all three editors: you specify a command that accepts an &lt;code&gt;acp&lt;/code&gt; argument, the editor spawns it, and they talk JSON-RPC. If your agent binary can speak the protocol, it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your Own ACP Agent
&lt;/h2&gt;

&lt;p&gt;If you're building a custom coding tool and want it to work in every ACP editor, official SDKs exist in five languages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;agent-client-protocol&lt;/code&gt; (crates.io)&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@agentclientprotocol/sdk&lt;/code&gt; (npm)&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;acp-sdk&lt;/code&gt; (PyPI)&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;acp-sdk&lt;/code&gt; (Maven Central)&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;acp-sdk&lt;/code&gt; (Maven Central)&lt;/td&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The minimal contract: implement the &lt;code&gt;initialize&lt;/code&gt; handshake, handle &lt;code&gt;session/new&lt;/code&gt; and &lt;code&gt;session/prompt&lt;/code&gt; methods, and send back content updates. The SDKs ship with example agents you can fork.&lt;/p&gt;

&lt;p&gt;Here's the skeleton in Python using the &lt;a href="https://agentclientprotocol.github.io/python-sdk/quickstart/" rel="noopener noreferrer"&gt;official SDK&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;acp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PromptResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_agent&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;user_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="c1"&gt;# ... call your LLM, read files, run terminal commands
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PromptResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s my response...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MyAgent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this with &lt;code&gt;python my_agent.py acp&lt;/code&gt; and point your editor's config at it. The SDK handles JSON-RPC framing, capability negotiation, and session lifecycle.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://agentclientprotocol.com/get-started/introduction" rel="noopener noreferrer"&gt;ACP specification&lt;/a&gt; covers file system access, terminal execution, slash commands, tool calls, and authentication. You don't need to implement all of them. Agents declare their capabilities during the &lt;code&gt;initialize&lt;/code&gt; handshake, and editors adapt their UI accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  When ACP Falls Short
&lt;/h2&gt;

&lt;p&gt;I've been using ACP daily for several months now, and it works well for what it covers. But there are gaps.&lt;/p&gt;

&lt;p&gt;The HTTP/WebSocket transport for remote agents is still work-in-progress. Every ACP agent runs as a local subprocess right now. That's fine for Claude Code or Gemini CLI, which run local binaries that call cloud APIs. It doesn't work for Devin-style cloud agents that need long-running sandboxed environments. There's an active RFC for remote transport, but nothing stable yet.&lt;/p&gt;

&lt;p&gt;Microsoft hasn't committed to native ACP support in VS Code. Their agent mode standardized on MCP for tool integration, and as of June 2026, Microsoft's position is that MCP covers their needs. The world's most popular editor requires community plugins for ACP. Those plugins exist but don't match the polish of native support in JetBrains or Zed.&lt;/p&gt;

&lt;p&gt;WSL isn't supported for ACP agents in JetBrains, which stings if your dev environment is WSL-based. You'll need to run agents in Windows-native mode or find workarounds. Zed on Windows handles ACP agents more cleanly, but Zed itself is newer on that platform.&lt;/p&gt;

&lt;p&gt;The registry is curated: all 50 agents must support user authentication. Experimental or internal agents need the manual config path. And the registry doesn't show usage stats, reviews, or compatibility notes. Just names and versions.&lt;/p&gt;

&lt;p&gt;I also found that agent quality varies. ACP standardizes the wire protocol, not what the agent does with it. Claude Agent, Gemini CLI, and Goose were reliably useful. Some of the smaller agents in the registry crashed on complex project structures or produced worse code than their non-ACP versions.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What is the Agent Client Protocol (ACP)?
&lt;/h3&gt;

&lt;p&gt;ACP is an open JSON-RPC 2.0 standard that lets AI coding agents communicate with code editors through a single protocol. Created by Zed Industries in August 2025 and co-maintained with JetBrains, it works like LSP but for AI agents instead of language servers. One agent implementation works across Zed, JetBrains, Neovim, and Emacs without separate plugins for each.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does ACP compare to MCP?
&lt;/h3&gt;

&lt;p&gt;They complement each other. ACP connects editors to agents (how an editor drives an AI assistant). MCP connects agents to tools (how an agent calls databases, APIs, or browser automation). In a typical stack, the editor uses ACP to talk to the agent, and the agent uses MCP to talk to external tools. You don't choose between them; you use both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which editors support ACP?
&lt;/h3&gt;

&lt;p&gt;As of June 2026: Zed (native, created the protocol), JetBrains IDEs (native, including IntelliJ, PyCharm, GoLand, WebStorm), Neovim (community plugin), and Emacs (community plugin). VS Code has community support through third-party extensions, but Microsoft hasn't added native ACP to VS Code itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI coding agents support ACP?
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://agentclientprotocol.com/registry" rel="noopener noreferrer"&gt;ACP Registry&lt;/a&gt; lists 50 agents including Claude Code, Gemini CLI, Codex CLI, GitHub Copilot, Cursor, Goose, Kiro, Junie, OpenCode, Grok Build, Mistral Vibe, and Qwen Code. The registry auto-updates hourly. Any agent not in the registry can be added manually through config files.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I set up an ACP agent in my editor?
&lt;/h3&gt;

&lt;p&gt;In JetBrains: open AI Chat &amp;gt; Install From ACP Registry &amp;gt; select your agent &amp;gt; enter your API key. In Zed: press Cmd+? &amp;gt; click + &amp;gt; select your agent. For custom agents, edit &lt;code&gt;~/.jetbrains/acp.json&lt;/code&gt; (JetBrains) or &lt;code&gt;~/.config/zed/settings.json&lt;/code&gt; (Zed) with the agent's command path and args.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://agentclientprotocol.com/get-started/introduction" rel="noopener noreferrer"&gt;Agent Client Protocol specification&lt;/a&gt; — official protocol documentation and getting started guide&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/agentclientprotocol/agent-client-protocol" rel="noopener noreferrer"&gt;ACP GitHub repository&lt;/a&gt; — specification source, SDKs, and governance (3.3k stars, Apache 2.0)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/agentclientprotocol/registry" rel="noopener noreferrer"&gt;ACP Registry&lt;/a&gt; — the 50-agent registry with auto-updated versions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.jetbrains.com/help/ai-assistant/acp.html" rel="noopener noreferrer"&gt;JetBrains ACP documentation&lt;/a&gt; — setup guide for JetBrains IDEs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zed.dev/docs/ai/external-agents" rel="noopener noreferrer"&gt;Zed external agents documentation&lt;/a&gt; — ACP setup in Zed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.marcnuri.com/agent-client-protocol-acp-introduction" rel="noopener noreferrer"&gt;Marc Nuri: ACP introduction&lt;/a&gt; — technical overview of ACP architecture and ACP vs MCP comparison&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;ACP is the most practical improvement to my AI coding workflow since MCP. The N×M problem (N editors times M agents, each needing a bespoke integration) was real, and ACP solves it. I run Claude Code in JetBrains, Gemini CLI in Zed, and switch between them depending on the task. The config files are ten lines of JSON.&lt;/p&gt;

&lt;p&gt;The protocol isn't finished. Remote agents need stable transport, VS Code needs native support, and the registry needs quality signals beyond "exists and authenticates." But the core promise (write one agent, run it everywhere) already works for the 50 agents and dozen-plus editor integrations that have adopted it.&lt;/p&gt;

&lt;p&gt;If you use more than one editor or more than one AI agent (and our &lt;a href="https://www.danilchenko.dev/posts/antigravity-cli-vs-claude-code/" rel="noopener noreferrer"&gt;terminal agent comparison&lt;/a&gt; suggests you should), set up ACP this weekend. It takes 15 minutes and saves you from maintaining three separate AI configurations that do the same thing.&lt;/p&gt;

</description>
      <category>agentclientprotocol</category>
      <category>acp</category>
      <category>aicoding</category>
      <category>jetbrains</category>
    </item>
    <item>
      <title>Google Jules Review: The Async Coding Agent Worth $20/Month?</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Fri, 22 May 2026 08:40:34 +0000</pubDate>
      <link>https://dev.to/dmaxdev/google-jules-review-the-async-coding-agent-worth-20month-4no</link>
      <guid>https://dev.to/dmaxdev/google-jules-review-the-async-coding-agent-worth-20month-4no</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Google Jules is the only major coding agent built around queuing instead of live chat. You describe a task, walk away, and a pull request shows up later. The free tier gives 15 tasks per day on Gemini 3 Flash. The $19.99/month Pro tier bumps that to 100 tasks on Gemini 3.1 Pro, and proactive features like CI Fixer and Scheduled Tasks make it feel less like a tool and more like a junior developer who never goes offline. But Jules is slow, can't handle files over ~50K lines, and only connects to GitHub. If you need real-time pair programming or work with GitLab, look elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Tried Jules
&lt;/h2&gt;

&lt;p&gt;I've been using &lt;a href="https://www.danilchenko.dev/posts/antigravity-cli-vs-claude-code/" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; and &lt;a href="https://www.danilchenko.dev/posts/claude-code-vs-codex-cli/" rel="noopener noreferrer"&gt;Codex CLI&lt;/a&gt; for months — both are real-time terminal agents where you type a prompt and watch code materialize. They're good at that. But I kept running into the same friction: I'd queue up three refactoring tasks in my head, then sit there babysitting the agent through each one sequentially. Context switching between "architect mode" and "watch the agent type" mode was costing me actual productive hours.&lt;/p&gt;

&lt;p&gt;Jules promised something different. Describe the task, hit submit, go do something else. Come back to a pull request. I signed up for the Pro tier ($19.99/month bundled with Google AI Pro) and spent three weeks throwing real work at it — dependency bumps, test scaffolding, bug fixes across a Flask API and two Go microservices.&lt;/p&gt;

&lt;p&gt;The short version: Jules delivered on the async promise. But "async" also means "slow," and the tradeoffs stack up in ways the marketing doesn't mention.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Jules Works
&lt;/h2&gt;

&lt;p&gt;Every task runs in an isolated Google Cloud VM. Jules clones your repo, reads the codebase, builds an execution plan, and shows you that plan before touching any files. You can edit the plan, approve it, or scrap it entirely. Once approved, Jules works through the changes file by file, running any tests it finds at each step. When it's done, it opens a PR on GitHub.&lt;/p&gt;

&lt;p&gt;The whole loop is: submit, approve a plan, wait for the PR notification. No terminal session, no streaming output, no watching characters appear.&lt;/p&gt;

&lt;p&gt;The model underneath depends on your tier. Free gets Gemini 3 Flash. Pro and Ultra run Gemini 3.1 Pro, which scores &lt;a href="https://www.swebench.com/viewer.html" rel="noopener noreferrer"&gt;80.6% on SWE-bench Verified&lt;/a&gt; — competitive with Claude Opus 4.6 at 80.8%, though behind Opus 4.7's 87.6% in agentic scaffolding. (For a full breakdown of how these models compare on coding tasks, see the &lt;a href="https://www.danilchenko.dev/posts/gpt-claude-gemini-coding/" rel="noopener noreferrer"&gt;GPT-5.4 vs Claude Opus 4.7 vs Gemini 3.1 Pro comparison&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing Breakdown
&lt;/h2&gt;

&lt;p&gt;Jules doesn't have its own subscription. It bundles into Google's AI tiers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Pro ($19.99/mo)&lt;/th&gt;
&lt;th&gt;Ultra ($99.99/mo)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Daily tasks&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent tasks&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Gemini 3 Flash&lt;/td&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;td&gt;Gemini 3.1 Pro (priority)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suggested Tasks&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled Tasks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free tier is generous enough for evaluation. Fifteen tasks per day covers most solo developers who want to offload grunt work. Pro makes sense once you're running 10+ tasks daily and want the model upgrade. Ultra is for teams running agent-heavy workflows — 60 concurrent tasks means you can point Jules at an entire sprint backlog and let it churn.&lt;/p&gt;

&lt;p&gt;One catch: paid plans require a @gmail.com account. Google Workspace users can't subscribe yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Jules Got Right
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Batch Parallelism
&lt;/h3&gt;

&lt;p&gt;The async model isn't just a UX gimmick. I'd queue five dependency-bump tasks at 9 AM, go write the design doc I'd been avoiding, and come back to five PRs by 10:30. With Claude Code, those same five tasks would take me through lunch because I'd be approving file edits and answering clarification prompts one by one.&lt;/p&gt;

&lt;p&gt;On Pro, 15 concurrent slots mean you can throw an entire backlog at Jules without hitting a queue. I ran 12 tasks simultaneously during a sprint cleanup, and all 12 completed within 90 minutes. Doing that sequentially in Claude Code would have taken most of an afternoon.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI Fixer
&lt;/h3&gt;

&lt;p&gt;This was the feature I didn't expect to love. When a GitHub Actions workflow fails, Jules automatically analyzes the logs, writes a fix, commits it, and resubmits. It loops until CI passes or gives up after a configurable number of attempts.&lt;/p&gt;

&lt;p&gt;I had a Flask test suite that broke after a SQLAlchemy upgrade. Three tests failing on a deprecated session API. I pointed Jules at the CI failure. It read the logs, traced the issue to &lt;code&gt;session.close()&lt;/code&gt; being called after the session was already garbage-collected, replaced it with a scoped session factory, and pushed a green build. Took about eight minutes. I would have spent 20 debugging that myself because I always forget the scoped session pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Tasks
&lt;/h3&gt;

&lt;p&gt;You can set Jules to run recurring jobs: nightly lint passes, weekly dependency audits, monthly dead-code sweeps. This is the part that makes Jules feel like a team member rather than a tool. I set up a weekly &lt;code&gt;pip-audit&lt;/code&gt; run on my Flask API — every Monday morning, a PR shows up with any new CVEs patched. Before Jules, I'd check this maybe once a quarter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Suggested Tasks
&lt;/h3&gt;

&lt;p&gt;On Pro and Ultra, Jules scans up to five repos and proposes improvements. It started with TODO comments — finding forgotten &lt;code&gt;# TODO: handle edge case&lt;/code&gt; annotations scattered through my code and opening PRs to actually handle them. Over two weeks, it cleared 14 TODOs I'd written months ago and forgotten about.&lt;/p&gt;

&lt;p&gt;The suggestions aren't always useful. Jules proposed refactoring a perfectly fine utility function into a class hierarchy that added complexity for zero benefit. But the hit rate was around 60-70%, and dismissing bad suggestions takes seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Jules Falls Short
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Speed
&lt;/h3&gt;

&lt;p&gt;Jules is slow. A task that Claude Code handles in 90 seconds takes Jules 8-15 minutes. Part of this is the VM spin-up, part is the planning phase (Jules builds a detailed plan before writing any code), and part is that Gemini 3.1 Pro generates tokens slower than Claude in agentic loops.&lt;/p&gt;

&lt;p&gt;For anything urgent (a production bug, a quick fix before a demo) Jules isn't the right tool. You'll be staring at a progress bar while Claude Code would have already pushed the commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Large File Blindness
&lt;/h3&gt;

&lt;p&gt;Gemini 3.1 Pro has a 1M-token context window, but Jules appears to impose a tighter limit in practice. Large files are off-limits. I hit this on a legacy Go service with a 12,000-line &lt;code&gt;handlers.go&lt;/code&gt; monolith (not proud of that file, but it exists). Jules's plan referenced functions that didn't exist in the file — it was working with a truncated view.&lt;/p&gt;

&lt;p&gt;Real-time agents handle this differently. Claude Code can stream file reads and focus on specific sections. Jules loads the whole context upfront and chokes on anything too large.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Only
&lt;/h3&gt;

&lt;p&gt;No GitLab. No Bitbucket. No self-hosted Git. If your repos aren't on github.com, Jules can't touch them. Google Workspace integration is also missing, which means enterprise teams on Google Cloud who use Cloud Source Repositores are locked out too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Coverage
&lt;/h3&gt;

&lt;p&gt;Python and TypeScript/JavaScript are first-class citizens. Jules writes solid code in both, catches edge cases, and uses idiomatic patterns. Go, Java, and C# work but with noticeably lower reliability. My Go microservices got PRs that compiled but missed patterns any Go developer would catch: unchecked errors, bare returns where wrapped errors belong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hallucinated Progress
&lt;/h3&gt;

&lt;p&gt;Twice during my testing, Jules claimed a task was complete when it had actually stalled mid-execution. The PR showed up with partial changes: half the files edited, tests not run. There's no clear indication in the UI when this happens. You find out during code review, which defeats the "queue and forget" promise. If you're relying on any coding agent for unsupervised work, &lt;a href="https://www.danilchenko.dev/posts/ai-agent-guardrails/" rel="noopener noreferrer"&gt;setting up guardrails&lt;/a&gt; before you go hands-off is worth the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jules vs the Competition
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Google Jules&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;GitHub Copilot Agent&lt;/th&gt;
&lt;th&gt;OpenAI Codex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interaction model&lt;/td&gt;
&lt;td&gt;Async (queue + PR)&lt;/td&gt;
&lt;td&gt;Real-time terminal&lt;/td&gt;
&lt;td&gt;Both (IDE + async)&lt;/td&gt;
&lt;td&gt;Async (cloud tasks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;$0–99.99/mo&lt;/td&gt;
&lt;td&gt;$20/mo (Pro) or API&lt;/td&gt;
&lt;td&gt;$10–39/mo&lt;/td&gt;
&lt;td&gt;API-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;td&gt;Claude Opus 4.7&lt;/td&gt;
&lt;td&gt;GPT-5.3-Codex (default)&lt;/td&gt;
&lt;td&gt;GPT-5.3-Codex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench&lt;/td&gt;
&lt;td&gt;80.6%&lt;/td&gt;
&lt;td&gt;87.6%&lt;/td&gt;
&lt;td&gt;~77–80%&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent tasks&lt;/td&gt;
&lt;td&gt;3–60&lt;/td&gt;
&lt;td&gt;1 (serial)&lt;/td&gt;
&lt;td&gt;1–3&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proactive features&lt;/td&gt;
&lt;td&gt;CI Fixer, Scheduled&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git platforms&lt;/td&gt;
&lt;td&gt;GitHub only&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;GitHub only&lt;/td&gt;
&lt;td&gt;GitHub only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Batch work, maintenance&lt;/td&gt;
&lt;td&gt;Complex refactors, exploration&lt;/td&gt;
&lt;td&gt;GitHub-native workflows&lt;/td&gt;
&lt;td&gt;Automated fixes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What counts here is workflow fit, not a feature checklist.&lt;/p&gt;

&lt;p&gt;Jules owns the batch maintenance lane. Queue 20 dependency bumps and lint fixes, check the PRs over coffee. On Pro with 15 concurrent slots, a full day's grunt work finishes before lunch. No other agent handles this volume as smoothly.&lt;/p&gt;

&lt;p&gt;Claude Code is the better pick for anything that needs back-and-forth. Debugging a race condition, designing an API, exploring unfamiliar code — you want a real-time thinking partner, and Opus 4.7's 7-point SWE-bench lead over Gemini 3.1 Pro shows up when the task gets hard. (I covered the &lt;a href="https://www.danilchenko.dev/posts/deepseek-v4-pro-review/" rel="noopener noreferrer"&gt;DeepSeek V4 Pro review&lt;/a&gt; recently, and it's another strong option at a fraction of Claude's API cost.)&lt;/p&gt;

&lt;p&gt;Copilot Agent fits if you already live in GitHub Issues and Actions. It's the least friction for teams whose entire workflow is PR-centric.&lt;/p&gt;

&lt;p&gt;Where Jules pulls ahead of all three: proactive features. I haven't found CI auto-fixing or scheduled recurring tasks in any competing agent. That gap alone kept me on the Pro tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Server Integration
&lt;/h2&gt;

&lt;p&gt;In February 2026, Jules added &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; support with six hand-selected servers: Linear, Stitch, Neon, Tinybird, Context7, and Supabase. Google took a curated approach: every server was audited for data flow and tool permissions before being allowed.&lt;/p&gt;

&lt;p&gt;In practice, this means Jules can read your Linear tickets, query your Neon database schema, and check Supabase auth configuration while planning changes. I connected the Neon MCP server and gave Jules a task: "add pagination to the /users endpoint based on the current schema." It pulled the schema directly from Neon, wrote the SQL migration and the Python endpoint code, and got it right on the first try. Without MCP, I'd have had to paste the schema into the task description.&lt;/p&gt;

&lt;p&gt;Six servers is limiting. Claude Code connects to any MCP server you configure. But Google's curated approach makes sense for an agent that runs in a cloud VM with repo access. A malicious MCP server could exfiltrate code, so restriction buys you something real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Jules API
&lt;/h2&gt;

&lt;p&gt;Google also launched a &lt;a href="https://developers.google.com/jules/api" rel="noopener noreferrer"&gt;Jules API&lt;/a&gt; for programmatic task creation. You can trigger Jules tasks from CI pipelines, chatbots, or custom tooling. The API exposes task creation, status polling, and result retrieval.&lt;/p&gt;

&lt;p&gt;The API is still in &lt;code&gt;v1alpha&lt;/code&gt;, so field names and auth methods may change. Here's the general shape of a session-creation call using the current schema:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-google-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jules.googleapis.com/v1alpha/sessions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Goog-Api-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sourceContext&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gitHub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;repository&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner/repo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;branch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Add input validation to /users POST endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# {"name": "sessions/abc123", "state": "CREATED", ...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;automationMode&lt;/code&gt; field controls whether Jules runs without human review of its execution plan. I keep it at the default (manual approval) because I want to see the plan before Jules starts editing files. For trusted, repeatable tasks like dependency bumps, switching to full automation turns Jules into an autonomous pipeline.&lt;/p&gt;

&lt;p&gt;The obvious next step is connecting Jules to your issue tracker: new bug filed, Jules automatically attempts a fix, PR shows up for review. The Stitch design team at Google reportedly runs "a pod of daily Jules agents" with assigned roles (performance tuning, security patching, accessibility, test coverage), making Jules, according to the team's blog post, one of the largest contributors to their repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Jitro: What's Coming Next
&lt;/h2&gt;

&lt;p&gt;Google previewed &lt;a href="https://byteiota.com/google-project-jitro-jules-v2-goal-driven-coding-agent/" rel="noopener noreferrer"&gt;Project Jitro&lt;/a&gt; at I/O 2026 — the next version of Jules that shifts from task-driven to goal-driven. Instead of "fix this function," you'd say "get test coverage to 85%" and Jitro figures out which files to change, which tests to write, and how to get the metric where you want it.&lt;/p&gt;

&lt;p&gt;The current Jules already hints at this direction. Suggested Tasks, Scheduled Tasks, and the Render integration all share one pattern: Jules initiating action based on codebase state. Jitro takes that to its logical conclusion.&lt;/p&gt;

&lt;p&gt;The obvious question is accountability. When an agent autonomously refactors modules to hit a metric, who reviews the architectural decisions it made along the way? Google hasn't answered that yet. Jitro launched under a waitlist at I/O, so general availability is probably months away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Use Jules
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You maintain multiple repos and spend hours weekly on dependency updates, lint fixes, and test scaffolding&lt;/li&gt;
&lt;li&gt;You want CI failures fixed automatically without context-switching from whatever you're building&lt;/li&gt;
&lt;li&gt;You work in Python or TypeScript and your repos are on GitHub&lt;/li&gt;
&lt;li&gt;You like reviewing PRs more than supervising an agent in real time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Skip it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need real-time collaboration — architecture discussions, exploratory coding, debugging complex state&lt;/li&gt;
&lt;li&gt;Your repos are on GitLab, Bitbucket, or self-hosted Git&lt;/li&gt;
&lt;li&gt;You work primarily in Go, Java, or C# where Jules's output needs heavy review anyway&lt;/li&gt;
&lt;li&gt;You need to work with files over 50K lines&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Is Google Jules free?
&lt;/h3&gt;

&lt;p&gt;Yes, the free tier gives 15 tasks per day with 3 concurrent slots, running on Gemini 3 Flash. No credit card required. It's enough to evaluate whether the async model fits your workflow before committing to Pro.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Google Jules compare to Claude Code?
&lt;/h3&gt;

&lt;p&gt;They solve different problems. Jules is async — you queue tasks and get PRs back later. Claude Code is real-time — you work together in a terminal session. Jules is better for batch maintenance work across multiple repos. Claude Code is better for complex single-task work where you need back-and-forth. Claude's underlying model (Opus 4.7, 87.6% SWE-bench) also outperforms Jules's Gemini 3.1 Pro (80.6%) on coding benchmarks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What languages does Google Jules support?
&lt;/h3&gt;

&lt;p&gt;Python and TypeScript/JavaScript are best supported. Go, Java, and C# work but produce less reliable output. Expect to catch missed error handling patterns and non-idiomatic code during review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Jules work with private repositories?
&lt;/h3&gt;

&lt;p&gt;Yes. Jules clones repos into isolated Google Cloud VMs. Google states your code isn't used for model training. The VM is ephemeral — spun up per task and destroyed after.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Project Jitro?
&lt;/h3&gt;

&lt;p&gt;Project Jitro is Google's next-generation coding agent, previewed at I/O 2026. Instead of describing a task ("fix this bug"), you define a goal ("reduce p95 latency by 30ms") and the agent determines the changes needed. It's on a waitlist — no general availability date yet.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jules.google/" rel="noopener noreferrer"&gt;Jules official site&lt;/a&gt; — product page with feature overview&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jules.google/docs/usage-limits/" rel="noopener noreferrer"&gt;Jules usage limits and pricing&lt;/a&gt; — tier breakdown and task quotas&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jules.google/docs/changelog/" rel="noopener noreferrer"&gt;Jules changelog&lt;/a&gt; — feature releases through 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.google/technology/developers/jules-proactive-updates/" rel="noopener noreferrer"&gt;Jules proactive features announcement&lt;/a&gt; — Suggested Tasks, Scheduled Tasks, Render integration&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://byteiota.com/google-project-jitro-jules-v2-goal-driven-coding-agent/" rel="noopener noreferrer"&gt;Project Jitro analysis&lt;/a&gt; — goal-driven agent architecture and timeline&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/jules/api" rel="noopener noreferrer"&gt;Jules API documentation&lt;/a&gt; — programmatic task creation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.swebench.com/viewer.html" rel="noopener noreferrer"&gt;SWE-bench Verified leaderboard&lt;/a&gt; — coding benchmark scores&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Jules is the best coding agent for people who hate babysitting coding agents. The async model, CI Fixer, and Scheduled Tasks create a workflow where maintenance work runs on autopilot. Monday mornings, I'd wake up to 3-4 PRs from overnight pip-audit and lint runs. For $19.99/month, that trade works.&lt;/p&gt;

&lt;p&gt;For thinking-partner work (debugging a race condition, designing an API, exploring unfamiliar code) you still need Claude Code or Copilot. Jules takes orders and delivers results, on its own schedule, at its own pace.&lt;/p&gt;

&lt;p&gt;If your bottleneck is "too many small tasks, not enough hands," try the free tier for a week. Queue up your backlog. See what comes back. The 15-task daily limit is enough to know whether this fits your workflow.&lt;/p&gt;

</description>
      <category>googlejules</category>
      <category>aicoding</category>
      <category>codingagents</category>
      <category>gemini</category>
    </item>
    <item>
      <title>AI Bug Bounty in 2026: 76% More Reports, Programs Shutting Down</title>
      <dc:creator>Maksim Danilchenko</dc:creator>
      <pubDate>Wed, 20 May 2026 08:35:23 +0000</pubDate>
      <link>https://dev.to/dmaxdev/ai-bug-bounty-in-2026-76-more-reports-programs-shutting-down-1a59</link>
      <guid>https://dev.to/dmaxdev/ai-bug-bounty-in-2026-76-more-reports-programs-shutting-down-1a59</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;AI-assisted vulnerability discovery has broken the bug bounty model. HackerOne paused its Internet Bug Bounty program, Curl killed its bounty payments (then quietly came back without them), and Linus Torvalds calls the Linux kernel's security mailing list "almost entirely unmanageable." Report volumes are up 76% year-over-year, but only 25% flag real flaws. The same AI models also found 500+ zero-days in major projects and drove CVE disclosure surges of 563% in Chrome and 476% in GitHub products. The security community is split between researchers who can't process the flood and AI tools that keep making it worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Inbox I Can't Keep Up With
&lt;/h2&gt;

&lt;p&gt;I run a small open-source project on the side. Nothing close to the scale of Curl or the Linux kernel, but enough to get the occasional security report through GitHub advisories. In early 2025, I'd see maybe one report a quarter. By March 2026, I got seven in a single week. Six of them cited functions that don't exist in my codebase.&lt;/p&gt;

&lt;p&gt;That experience made me pay close attention when Daniel Stenberg, who maintains Curl (a tool installed on basically every server on Earth), &lt;a href="https://socket.dev/blog/curl-shuts-down-bug-bounty-program-after-flood-of-ai-slop-reports" rel="noopener noreferrer"&gt;killed his bug bounty payments&lt;/a&gt; at the end of January 2026. His reasoning was blunt: fewer than 5% of submitted reports in 2025 were legitimate. The rest were what the security community now calls "AI slop," plausible-sounding reports generated by language models that reference imaginary functions, fabricate patches, and waste hours of maintainer time.&lt;/p&gt;

&lt;p&gt;Stenberg's frustration was raw. His updated security.txt file now reads: "We will ban you and ridicule you in public if you waste our time on crap reports."&lt;/p&gt;

&lt;p&gt;A month later, Curl returned to HackerOne without monetary rewards. By April, Stenberg said "the slop situation is not a problem anymore" and the confirmed vulnerability rate was back above 15%. Removing the financial incentive worked for Curl. Most other projects aren't so lucky.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Flood by the Numbers
&lt;/h2&gt;

&lt;p&gt;HackerOne, the largest bug bounty platform, reports a &lt;a href="https://www.hackerone.com/resources/hackerone-2026-security-report" rel="noopener noreferrer"&gt;76% jump in submissions&lt;/a&gt; year-over-year through March 2026. The share flagging real vulnerabilities held at 25%. That means the 76% increase is almost entirely noise.&lt;/p&gt;

&lt;p&gt;Bugcrowd, which runs bounty programs for OpenAI, T-Mobile, and Motorola, watched its inbox &lt;a href="https://www.axios.com/2026/03/10/ai-agents-spam-the-volunteers-securing-open-source-software" rel="noopener noreferrer"&gt;swell more than fourfold&lt;/a&gt; during a three-week stretch in March. Most of what came in was unusable.&lt;/p&gt;

&lt;p&gt;Before AI tools entered the picture, a popular open-source project might get two or three bug reports in a week. Less popular ones, maybe one a month. Now some projects are getting hundreds at a time, and the overwhelming majority cite non-existent code paths, imaginary patches or vague theoretical attacks that fall apart under any scrutiny.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Shut Down and Why
&lt;/h2&gt;

&lt;p&gt;Several programs have paused or shut down in the first five months of 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project / Platform&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;HackerOne&lt;/strong&gt; (Internet Bug Bounty)&lt;/td&gt;
&lt;td&gt;Paused all new submissions&lt;/td&gt;
&lt;td&gt;March 27, 2026&lt;/td&gt;
&lt;td&gt;Discovery outpacing remediation capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Curl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Killed bounty payments; returned to HackerOne without rewards Feb 25&lt;/td&gt;
&lt;td&gt;January 31, 2026&lt;/td&gt;
&lt;td&gt;&amp;lt;5% legitimate reports, maintainer burnout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raised quality bar for AI-submitted reports&lt;/td&gt;
&lt;td&gt;May 2026&lt;/td&gt;
&lt;td&gt;Quality threshold not met&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node.js&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Paused bug bounty&lt;/td&gt;
&lt;td&gt;April 2026&lt;/td&gt;
&lt;td&gt;Lost HackerOne funding, no independent budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Django&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Modified submission process&lt;/td&gt;
&lt;td&gt;Q1 2026&lt;/td&gt;
&lt;td&gt;Report volume overwhelming volunteer team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;libxml2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ended embargoed vulnerability reports&lt;/td&gt;
&lt;td&gt;June 2025&lt;/td&gt;
&lt;td&gt;Maintainer capacity exceeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nextcloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shut down bounty program&lt;/td&gt;
&lt;td&gt;April 2026&lt;/td&gt;
&lt;td&gt;Unsustainable maintainer workload&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;HackerOne's pause was the biggest signal. The platform &lt;a href="https://www.darkreading.com/application-security/ai-led-remediation-crisis-prompts-hackerone-pause-bug-bounties" rel="noopener noreferrer"&gt;cited a direct link&lt;/a&gt; between AI-assisted research and the imbalance: discovery used to be the bottleneck, but with automated discovery, &lt;em&gt;remediation&lt;/em&gt; is now the bottleneck. Bounty programs don't fund remediation.&lt;/p&gt;

&lt;p&gt;Christopher Robinson, CTO of the Open Source Security Foundation: "If it takes a maintainer two to eight hours of unbudgeted, unallocated time [per report], that becomes burdensome."&lt;/p&gt;

&lt;p&gt;For a project like Curl with a small team of active maintainers, the math stopped working. Stenberg moved security intake to GitHub Security Advisories, then returned to HackerOne without bounty payments. His warning to anyone thinking of submitting a report generated by a language model: he'd consider an entrance fee for reporters next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Torvalds Quote
&lt;/h2&gt;

&lt;p&gt;Linus Torvalds doesn't mince words on a quiet day. On the subject of AI-generated security reports, he was characteristically direct.&lt;/p&gt;

&lt;p&gt;"If you found a bug using AI tools," he wrote in his weekly kernel release post, "the chances are somebody else found it too."&lt;/p&gt;

&lt;p&gt;The Linux kernel's security mailing list, where critical vulnerabilities get reported before public disclosure, is now &lt;a href="https://www.helpnetsecurity.com/2026/05/18/problems-with-ai-assisted-vulnerability-research/" rel="noopener noreferrer"&gt;"almost entirely unmanageable, with enormous duplication due to different people finding the same things with the same tools."&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A dozen independent researchers each feed the same Linux kernel source into Claude or GPT, find the same buffer overflow, and each submit a separate report believing they've discovered something novel. The maintainers on the other end receive twelve versions of the same finding, each padded with AI-generated analysis that needs to be triaged individually. Multiply that across every subsystem and you've got a mailing list that requires dedicated staff just to process — staff the kernel project doesn't have.&lt;/p&gt;

&lt;p&gt;Torvalds's advice to AI-assisted researchers: "Don't be the drive-by 'send a random report with no real understanding' kind of person."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Matplotlib Incident
&lt;/h2&gt;

&lt;p&gt;Matplotlib maintainer Scott Shambaugh got a front-row seat to the absurdity.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.danilchenko.dev/posts/github-ai-agents-pull-requests/" rel="noopener noreferrer"&gt;AI agent PR flood on GitHub&lt;/a&gt; has a security twin. An AI agent submitted a pull request to the Matplotlib project. Shambaugh reviewed it, found it insufficient, and rejected it. The agent (not the human operator, but the autonomous agent itself) then &lt;a href="https://www.axios.com/2026/03/10/ai-agents-spam-the-volunteers-securing-open-source-software" rel="noopener noreferrer"&gt;published a disparaging blog post&lt;/a&gt; about Shambaugh on the internet. It later apologized on GitHub.&lt;/p&gt;

&lt;p&gt;An AI agent wrote a hit piece about an open-source maintainer because he rejected its pull request — in February 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  But AI Is Also Finding Real Zero-Days
&lt;/h2&gt;

&lt;p&gt;The same tools generating the flood of junk reports are also finding genuine, high-severity vulnerabilities that human researchers missed for years. I wrote about &lt;a href="https://www.danilchenko.dev/posts/claude-500-zero-days/" rel="noopener noreferrer"&gt;Claude finding 500+ zero-days&lt;/a&gt; in April, and the numbers have gotten more dramatic since.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.vulncheck.com/blog/ai-assisted-vulnerability-discovery" rel="noopener noreferrer"&gt;VulnCheck's analysis&lt;/a&gt; of CVE disclosure volumes in 2026 tells the other side of the story:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;CVE Disclosure Change (YoY, 2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;+563.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub products&lt;/td&gt;
&lt;td&gt;+476.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VMware&lt;/td&gt;
&lt;td&gt;+180.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache&lt;/td&gt;
&lt;td&gt;+170.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mozilla Firefox&lt;/td&gt;
&lt;td&gt;+156.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HPE&lt;/td&gt;
&lt;td&gt;+132.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F5&lt;/td&gt;
&lt;td&gt;+113.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Palo Alto Networks&lt;/td&gt;
&lt;td&gt;+37.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those aren't hypothetical. Chrome's CVE disclosures are up 563% year over year. Mozilla confirmed in February 2026 that it's now using frontier AI models internally to find and fix latent browser vulnerabilities. Anthropic's Claude Mythos, through &lt;a href="https://www.vulncheck.com/blog/ai-assisted-vulnerability-discovery" rel="noopener noreferrer"&gt;Project Glasswing&lt;/a&gt; (announced April 7, 2026), has been made available to AWS, Apple, Broadcom, Cisco, CrowdStrike, Google, JPMorgan Chase, the Linux Foundation, Microsoft, NVIDIA, and Palo Alto Networks specifically for defensive vulnerability hunting. Anthropic stated that Mythos "identified thousands of zero-day vulnerabilities across every major operating system and web browser."&lt;/p&gt;

&lt;p&gt;Concrete wins include ActiveMQ CVE-2026-34197, discovered by researcher Naveen Sunkavally using Claude assistance. That CVE is now actively exploited in the wild and appears on CISA's Known Exploited Vulnerabilities list. Stanislav Fort's AISLE tool found all 12 CVEs in OpenSSL's January 2026 coordinated release and is credited with 13 of 14 OpenSSL CVEs across recent releases. Anthropic gave the Apache Software Foundation &lt;a href="https://www.vulncheck.com/blog/ai-assisted-vulnerability-discovery" rel="noopener noreferrer"&gt;$1.5 million&lt;/a&gt; specifically to help Apache handle the AI-driven vulnerability flood.&lt;/p&gt;

&lt;p&gt;And Curl itself, despite Stenberg's fury at junk submissions, credited AI-assisted tools with &lt;a href="https://www.axios.com/2026/03/10/ai-agents-spam-the-volunteers-securing-open-source-software" rel="noopener noreferrer"&gt;helping fix around 170 bugs&lt;/a&gt; that survived years of aggressive fuzzing and multiple human security audits. Researcher Joshua Rogers used AI tools to systematically analyze the Curl codebase before submitting high-quality reports.&lt;/p&gt;

&lt;p&gt;The catch: when Stenberg tested Anthropic's Mythos specifically against Curl, only 1 of 5 reported vulnerabilities held up as a valid CVE. Even the best models have a meaningful false positive rate on real-world codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Signal-to-Noise Problem
&lt;/h2&gt;

&lt;p&gt;AI finds vulnerabilities. The CVE data above removes any doubt. But the economics of bug bounty programs assumed a world where discovery was expensive.&lt;/p&gt;

&lt;p&gt;When finding a buffer overflow required deep knowledge of C memory management, familarity with the specific codebase, and hours of manual source review, the friction itself acted as a quality filter. Researchers who submitted reports had usually done genuine work. Bounty payments were both reward and incentive: you invested effort, you got paid for real findings.&lt;/p&gt;

&lt;p&gt;AI collapsed that friction. Now anyone can paste a codebase into a model's context window and get back something that looks like a vulnerability report. The API call costs under a dollar. The "researcher" may have no idea whether the finding is real, but the report reads well enough to require a maintainer to spend time disproving it.&lt;/p&gt;

&lt;p&gt;Bugcrowd and HackerOne are building AI-powered filtering tools to help customers triage the volume. HackerOne introduced what it calls "agentic validation capabilities," using AI to check whether AI-generated reports are real. The recursion is absurd, but it may be the only path that scales.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern Behind Real AI-Found Vulnerabilities
&lt;/h2&gt;

&lt;p&gt;Not all AI-assisted security research is junk. The projects that produce real findings share a pattern. Based on what's worked (AISLE on OpenSSL, Rogers on Curl, Mozilla's internal team, the ActiveMQ discovery), the effective approach looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Simplified pattern: effective AI-assisted vuln research
# 1. Targeted scope (one library, one attack surface)
# 2. Model-assisted analysis + human verification
# 3. Working proof of concept before submission
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_finding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vuln_report&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Before submitting any AI-found vulnerability,
    verify it with a working PoC.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 1: Does the function actually exist?
&lt;/span&gt;    &lt;span class="n"&gt;source_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vuln_report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;function_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vuln_report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-rn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_file&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# AI hallucinated the function
&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 2: Can you trigger the bug?
&lt;/span&gt;    &lt;span class="n"&gt;poc_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_proof_of_concept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vuln_report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;poc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;poc_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crashed&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;poc_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leaked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Theoretical, not exploitable
&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 3: Is it already known?
&lt;/span&gt;    &lt;span class="n"&gt;known&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_cve_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vuln_report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;known&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Duplicate
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between Rogers's ~170 valid Curl findings and the thousands of junk submissions is straightforward: Rogers verified before submitting. He understood the Curl codebase, used AI to accelerate analysis, and only reported what he could prove.&lt;/p&gt;

&lt;p&gt;Stanislav Fort, founder of AISLE, has said his tool finds bugs that existing automated methods couldn't reach. The value is in extending what's findable past the limits of manual review and traditional fuzzing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Maintainers Should Do Right Now
&lt;/h2&gt;

&lt;p&gt;If you maintain an open-source project of any size, the AI report flood is coming for your inbox (if it hasn't already). Based on how the larger projects have responded, here's what's working:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Move intake off bounty platforms. Curl's switch to GitHub Security Advisories with no monetary rewards cut junk submissions dramatically. The financial incentive was attracting the worst actors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Django and several Linux subsystems now reject any report that doesn't include a working exploit or at minimum a reproduction script. Require a proof of concept. "Theoretical attack scenario" doesn't cut it anymore.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Template your rejections. Stenberg's blunt approach saves time: a canned response for reports that cite non-existent functions, with a clear warning about bans for repeated offenses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub's Jarom Brown confirmed that programs across the industry are &lt;a href="https://www.helpnetsecurity.com/2026/05/18/problems-with-ai-assisted-vulnerability-research/" rel="noopener noreferrer"&gt;building automated filters&lt;/a&gt;. Even a simple check ("does this function name exist in our codebase?") would eliminate a huge percentage of AI slop. If you can automate triage, do it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't blanket-ban AI tools. Rogers, Fort, and Mozilla's internal team show that AI-assisted discovery done right produces results manual review can't match. Ban lazy submissions, not the tooling. If you're running AI agents in your own workflow, setting up proper &lt;a href="https://www.danilchenko.dev/posts/ai-agent-guardrails/" rel="noopener noreferrer"&gt;guardrails&lt;/a&gt; helps on the other side of the equation too.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who Pays for Remediation?
&lt;/h2&gt;

&lt;p&gt;Anthropic's $1.5 million grant to Apache is the only large-scale example of an AI lab paying for the downstream cost of its models' vulnerability discoveries. Compare that to the scale of the problem: the Apache Software Foundation handles security for projects used by every major tech company on Earth. A million and a half dollars won't sustain a team to process the current volume, let alone the volume that's coming as AI models get better.&lt;/p&gt;

&lt;p&gt;HackerOne's original framing was correct: discovery used to be the bottleneck, and bounties funded it. Now remediation is the bottleneck, and nobody funds it. Open source maintainers are volunteers. When AI sends them hundreds of reports a week, each requiring two to eight hours to evaluate, the math breaks down fast.&lt;/p&gt;

&lt;p&gt;There's a real possibility that some open-source projects will simply stop accepting security reports altogether rather than drown in triage. That would be a worse outcome than the current mess: real vulnerabilities going unfixed because the signal is buried under too much noise.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  How is AI affecting bug bounty programs?
&lt;/h3&gt;

&lt;p&gt;AI has massively increased the volume of vulnerability reports while keeping the rate of legitimate findings flat at around 25%. HackerOne saw a 76% jump in submissions year-over-year through March 2026, and Bugcrowd's inbox swelled fourfold in three weeks. Several programs, including HackerOne's Internet Bug Bounty, Node.js, and Nextcloud, have paused or shut down. Curl killed its bounty payments, returned without them, and now filters more aggressively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why did HackerOne pause its Internet Bug Bounty?
&lt;/h3&gt;

&lt;p&gt;HackerOne paused new submissions on March 27, 2026, citing a shift from discovery to remediation as the bottleneck. AI-assisted research has accelerated vulnerability discovery past the point where open-source maintainers can keep up with fixes. The program was designed for a world where finding bugs was expensive, and that assumption collapsed in under a year.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can AI replace human bug bounty hunters?
&lt;/h3&gt;

&lt;p&gt;Not yet. The most effective AI-assisted findings (AISLE's OpenSSL work, Rogers's Curl audits, Mozilla's internal team) all involve human verification and deep codebase knowledge. AI excels at scanning large codebases for patterns that fuzzing misses, but it also hallucinates functions and fabricates attack scenarios. The value comes when experienced researchers use AI to scan at scale, then verify each finding by hand before submitting.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is AI slop in security reports?
&lt;/h3&gt;

&lt;p&gt;"AI slop" refers to low-quality vulnerability reports generated by language models that are submitted without human verification. Typical characteristics: citing functions that don't exist in the codebase, proposing patches for imaginary code paths, presenting theoretical attacks with no proof of concept, and padding reports with verbose but vacuous analysis. Curl's Daniel Stenberg reported that fewer than 5% of reports received in 2025 were legitimate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are AI-generated vulnerability reports legitimate?
&lt;/h3&gt;

&lt;p&gt;Some are. Chrome's CVE disclosures are up 563% year-over-year, and AI tools are credited with finding real zero-days including ActiveMQ CVE-2026-34197 (now on CISA's KEV list) and all 12 OpenSSL CVEs from January 2026. But the majority of AI-generated submissions to public bounty programs are not legitimate. They lack working proofs of concept and often reference code that doesn't exist.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.hackerone.com/resources/hackerone-2026-security-report" rel="noopener noreferrer"&gt;HackerOne 2026 Security Report&lt;/a&gt; — primary source for the 76% submission increase and 25% legitimate-finding rate&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.helpnetsecurity.com/2026/05/18/problems-with-ai-assisted-vulnerability-research/" rel="noopener noreferrer"&gt;Help Net Security — AI is drowning software maintainers in junk security reports&lt;/a&gt; — Torvalds quotes, industry response, Jarom Brown on automated filters&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.vulncheck.com/blog/ai-assisted-vulnerability-discovery" rel="noopener noreferrer"&gt;VulnCheck — The First CVE Wave: AI-Assisted Vulnerability Discovery Is Reshaping Disclosure Volumes&lt;/a&gt; — CVE disclosure data, Project Glasswing details, ActiveMQ CVE, Apache $1.5M grant&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.axios.com/2026/03/10/ai-agents-spam-the-volunteers-securing-open-source-software" rel="noopener noreferrer"&gt;Axios — AI agents are flooding open-source maintainers with security reports&lt;/a&gt; — Matplotlib incident, report volume statistics, OSSF quotes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://socket.dev/blog/curl-shuts-down-bug-bounty-program-after-flood-of-ai-slop-reports" rel="noopener noreferrer"&gt;Socket.dev — Curl Shuts Down Bug Bounty Program After Flood of AI Slop Reports&lt;/a&gt; — Stenberg quotes, Curl program timeline, Django/Node.js/libxml2 moves&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI has broken the bug bounty model in 2026, and nobody has a working replacement yet. The same models generating mountains of junk reports are also finding real zero-days that human researchers missed for decades. Chrome's CVE disclosures are up 563%. Anthropic is handing out $1.5 million grants to help projects cope. Curl's maintainer is threatening to charge admission for security reporters.&lt;/p&gt;

&lt;p&gt;The projects getting this right (AISLE, Mozilla, Rogers on Curl) share one thing: human expertise doing the verification, AI doing the scanning at scale. The projects drowning are the ones where the reports arrive faster than anyone can read them.&lt;/p&gt;

&lt;p&gt;Security researchers using AI tools: verify before you submit. Maintainers: strip the financial incentive from your intake process and require proof of concept. As for the AI labs whose models are generating this flood, Anthropic's $1.5 million to Apache is a start. The tab is going to be a lot higher than that.&lt;/p&gt;

</description>
      <category>aisecurity</category>
      <category>bugbounty</category>
      <category>opensource</category>
      <category>vulnerabilitydiscovery</category>
    </item>
  </channel>
</rss>
