<?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: ArshTechPro</title>
    <description>The latest articles on DEV Community by ArshTechPro (@arshtechpro).</description>
    <link>https://dev.to/arshtechpro</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%2F3258664%2F7a2cc61a-0b4d-4cf8-884e-52f33905cac3.png</url>
      <title>DEV Community: ArshTechPro</title>
      <link>https://dev.to/arshtechpro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arshtechpro"/>
    <language>en</language>
    <item>
      <title>Humanizer: The 40k-Star Agent Skill That Strips AI Tells From Your Writing</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 03 Sep 2026 10:43:45 +0000</pubDate>
      <link>https://dev.to/arshtechpro/humanizer-the-40k-star-agent-skill-that-strips-ai-tells-from-your-writing-4758</link>
      <guid>https://dev.to/arshtechpro/humanizer-the-40k-star-agent-skill-that-strips-ai-tells-from-your-writing-4758</guid>
      <description>&lt;p&gt;Wikipedia editors have a problem. People keep dumping LLM output into articles, and it reads badly in a specific, recognizable way. So WikiProject AI Cleanup wrote up a page cataloguing the tells: the inflated importance, the forced triads, the "not just X, it's Y" construction, the paragraph that ends by assuring you the future looks bright.&lt;/p&gt;

&lt;p&gt;Siqi Chen looked at that page and had an idea that is obvious in retrospect. If someone has already collated a detailed list of how AI writing gives itself away, you can hand that list to an LLM and tell it not to do any of it. He asked Claude Code to read the article and build a skill from it.&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://github.com/blader/humanizer" rel="noopener noreferrer"&gt;blader/humanizer&lt;/a&gt;, now sitting at roughly 40k stars.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is a Markdown file
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises people. Humanizer is a &lt;code&gt;SKILL.md&lt;/code&gt; file. There is no package, no model, no API, no dependency tree. It is instructions.&lt;/p&gt;

&lt;p&gt;That sounds like a limitation and is actually the reason it spread. Because it is plain Markdown following the Agent Skills format, it runs on anything that supports skills. And if your tool does not support skills, you can open the file and paste it into a system prompt. Distribution problem solved by not having one.&lt;/p&gt;

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

&lt;p&gt;Humanizer encodes 35 patterns drawn from Wikipedia's "Signs of AI writing" page. Each one is a named failure mode with a before and after example, which matters more than it sounds: the model is not being told "write naturally," which is useless advice. It is being told "you wrote 'serves as a testament to' and here is the specific replacement."&lt;/p&gt;

&lt;p&gt;The run is two passes. It rewrites first, without treating the original paragraph structure as fixed. Then it checks that draft against the 35 patterns and against the original claims, and rewrites whatever is still wrong. When you paste text in, it shows you both stages plus a short critique of what still sounds artificial, which is genuinely useful for learning your own tells.&lt;/p&gt;

&lt;p&gt;The patterns fall into five groups:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content.&lt;/strong&gt; Inflated importance, name-dropping, vague attribution ("experts believe"), the formulaic "despite challenges, it continues to thrive" arc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language and grammar.&lt;/strong&gt; Overused AI vocabulary (testament, landscape, showcasing, delve-adjacent stuff), avoiding plain "is" and "has" in favor of "serves as" and "boasts," the not-X-but-Y construction, forced groups of three, fake "from X to Y" ranges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Style.&lt;/strong&gt; Em dashes, excessive bold, lists where prose would do, title case headings, emojis, curly quotes, hyphen pileups like "cross-functional, data-driven, client-facing," manufactured profundity ("at its core, what matters is"), and fake-candid openings ("Honestly? It depends").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chatbot residue.&lt;/strong&gt; "I hope this helps," knowledge-cutoff disclaimers, "Great question!"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filler.&lt;/strong&gt; "In order to" instead of "to." Stacked hedges like "could potentially possibly." Endings that gesture vaguely at a bright future.&lt;/p&gt;

&lt;p&gt;A quick illustration of the shape of the fix:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; The migration serves as a testament to our team's commitment to scalability, reliability, and performance — proving that with the right architecture, the future looks bright.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; The migration cut p99 latency from 1.2s to 340ms. We still have two services on the old queue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same topic. One sentence says nothing and one says something.&lt;/p&gt;

&lt;p&gt;Two design decisions deserve credit. First, there is a hard no-fabrication rule, added in v2.9.0. Names, numbers, dates, quotes, and citations have to come from the source or from you. If a detail is missing, it is supposed to ask rather than invent one, which is exactly the failure mode you would expect from a tool whose job is to make text sound more lived-in and specific. Second, if you paste in two or three paragraphs of your own writing as a sample, it follows your rhythm and quirks instead of its default style rules. That is the difference between "sounds human" and "sounds like you."&lt;/p&gt;

&lt;p&gt;Pointed at a file, it only touches prose. Code blocks, data, frontmatter, and link targets stay put.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it work with Claude?
&lt;/h2&gt;

&lt;p&gt;Yes, three different ways, and the repo has clearly been maintained with Claude in mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;, via the skills CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add blader/humanizer &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop &lt;code&gt;--global&lt;/code&gt; to install into the current project only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code as a plugin&lt;/strong&gt; (2.1.142 or newer):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add blader/humanizer
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;humanizer@humanizer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invoke it with &lt;code&gt;/humanizer:humanizer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Desktop&lt;/strong&gt;: download the repo as a ZIP and upload it as a skill. This one was broken for a while. The repo used a symlink for the plugin path, and Claude Desktop could not handle it in the source archive. It was fixed twice, first with a dedicated release asset in v2.11.1, then properly in v2.11.2 by dropping the symlink entirely once Claude Code began loading the root &lt;code&gt;SKILL.md&lt;/code&gt; directly. If you tried this months ago and it failed, try again.&lt;/p&gt;

&lt;p&gt;Once installed, either invocation style works:&lt;br&gt;
&lt;/p&gt;

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

[paste your text here]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just ask for it in plain language, including pointing at a path: "Humanize the prose in docs/launch-post.md."&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it work with other agents?
&lt;/h2&gt;

&lt;p&gt;Also yes, and this is where the "it's just Markdown" decision pays off. The &lt;code&gt;npx skills&lt;/code&gt; CLI targets 40-plus agents, and Humanizer is a normal skill package, so it installs to any of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add blader/humanizer &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--agent&lt;/span&gt; codex
npx skills add blader/humanizer &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--agent&lt;/span&gt; cursor &lt;span class="nt"&gt;--agent&lt;/span&gt; opencode
npx skills add blader/humanizer &lt;span class="nt"&gt;--global&lt;/span&gt; &lt;span class="nt"&gt;--agent&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common targets and where the files land:&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;
&lt;code&gt;--agent&lt;/code&gt; flag&lt;/th&gt;
&lt;th&gt;Global path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;claude-code&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;codex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.codex/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cursor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.cursor/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini-cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.gemini/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;github-copilot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.copilot/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opencode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.opencode/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amp&lt;/td&gt;
&lt;td&gt;&lt;code&gt;amp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.config/agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.cline/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windsurf&lt;/td&gt;
&lt;td&gt;&lt;code&gt;windsurf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.windsurf/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Kiro, Qwen Code, Roo, Trae, Goose, Continue, and others are supported too. Remember to reload skills in the target agent afterward.&lt;/p&gt;

&lt;p&gt;For anything not on that list, the manual path always works: copy &lt;code&gt;SKILL.md&lt;/code&gt; into the agent's skill folder. And for a plain chat interface with no skill support at all, open &lt;code&gt;SKILL.md&lt;/code&gt;, copy the contents, and paste it as your system prompt or as the preamble to your request. You lose the slash command and the file-editing mode. The 35 patterns still apply.&lt;/p&gt;

&lt;p&gt;The honest caveat on cross-agent use: the skill is a prompt, so results depend on how well the underlying model follows a long instruction set. A frontier model with good instruction-following will apply all 35 patterns and self-critique properly. A smaller local model may catch the obvious vocabulary swaps and quietly skip the harder judgment calls. Same file, different mileage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It is not an AI-detector defeat button.&lt;/strong&gt; People reach for it that way and the framing is wrong in both directions. Detectors are unreliable enough that they flag human writing regularly, so "passing" one proves little. And Humanizer's actual job is removing bad writing habits, not adversarially targeting a classifier. If you are trying to sneak past an academic integrity check, this is the wrong tool and a bad plan. If you are trying to make a launch post not read like a launch post, it is the right tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some patterns are opinions, not rules.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It cannot add substance.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overcorrection is real.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Worth using?
&lt;/h2&gt;

&lt;p&gt;For a Markdown file with an MIT license, the cost of trying it is close to zero, and it is the rare tool where reading the source teaches you something even if you never install it. The 35 patterns are a decent editing checklist on their own.&lt;/p&gt;

&lt;p&gt;The realistic workflow is not "generate with AI, run Humanizer, ship." It is: write the draft, make sure it contains real specifics only you have, then run Humanizer to strip the stylistic residue, then read the output yourself. It is a de-slopping pass, not a ghostwriter.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/blader/humanizer" rel="noopener noreferrer"&gt;github.com/blader/humanizer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
    <item>
      <title>TimesFM: Google's Foundation Model for Time Series, Explained for Developers</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Thu, 03 Sep 2026 10:21:07 +0000</pubDate>
      <link>https://dev.to/arshtechpro/timesfm-googles-foundation-model-for-time-series-explained-for-developers-3jf9</link>
      <guid>https://dev.to/arshtechpro/timesfm-googles-foundation-model-for-time-series-explained-for-developers-3jf9</guid>
      <description>&lt;p&gt;TimesFM proposes a different deal: skip the training step entirely.&lt;/p&gt;

&lt;p&gt;It is a pretrained model from Google Research that forecasts time series it has never seen before. Same idea as an LLM, except instead of predicting the next word, it predicts the next value. You hand it a NumPy array of history, tell it how far ahead to look, and get a forecast back. No training loop, no hyperparameter search.&lt;/p&gt;

&lt;p&gt;This post covers what it actually does, how the API feels, what landed in the brand-new 3.0 release.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model: patches are tokens
&lt;/h2&gt;

&lt;p&gt;An LLM chops text into tokens and learns to predict the next one. TimesFM chops a time series into &lt;strong&gt;patches&lt;/strong&gt; (contiguous windows of 32 time steps) and learns to predict the next patch.&lt;/p&gt;

&lt;p&gt;That is basically the whole idea. A decoder-only transformer, the same family as GPT, pointed at numbers instead of text.&lt;/p&gt;

&lt;p&gt;Patching matters for two practical reasons. Attention cost grows with sequence length, so grouping 32 points into one token keeps long histories affordable. And a patch captures a local shape (a weekly cycle, a spike, a slow drift) as a single unit, which is closer to how time series actually behave than treating each individual point as a token.&lt;/p&gt;

&lt;p&gt;The training data is the other half of the story. TimesFM was pretrained on a corpus of over a trillion time points spanning retail, finance, web traffic, energy, and synthetic data. It has seen enough patterns of "sales-shaped thing" and "traffic-shaped thing" that when you hand it yours, it recognizes the family. That is what makes zero-shot forecasting work.&lt;/p&gt;

&lt;p&gt;You also get uncertainty for free. The model outputs 9 quantiles (10th through 90th percentile) at every step, not just a single line. If you have ever had to bolt confidence intervals onto a forecast after the fact, this is a real convenience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Installation is a one-liner:&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;timesfm[torch]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from source with &lt;code&gt;uv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/google-research/timesfm.git
&lt;span class="nb"&gt;cd &lt;/span&gt;timesfm
uv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
uv pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; .[torch]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The 2.5 API
&lt;/h3&gt;

&lt;p&gt;This is the API most tutorials and existing code use. You load a checkpoint, compile it once with a config, then call &lt;code&gt;forecast()&lt;/code&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;torch&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timesfm&lt;/span&gt;

&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_float32_matmul_precision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timesfm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimesFM_2p5_200M_torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google/timesfm-2.5-200m-pytorch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;timesfm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForecastConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_context&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;max_horizon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;normalize_inputs&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;use_continuous_quantile_head&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;force_flip_invariance&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;infer_is_positive&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;fix_quantile_crossing&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="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;point_forecast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantile_forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;horizon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;point_forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;     &lt;span class="c1"&gt;# (2, 12)
&lt;/span&gt;&lt;span class="n"&gt;quantile_forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;  &lt;span class="c1"&gt;# (2, 12, 10)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth noticing. The inputs are plain 1D arrays and they do &lt;strong&gt;not&lt;/strong&gt; have to be the same length, which is genuinely nice when you are forecasting a batch of products with different histories. And a few config flags do real work: &lt;code&gt;infer_is_positive&lt;/code&gt; stops the model predicting negative sales, &lt;code&gt;fix_quantile_crossing&lt;/code&gt; prevents the 60th percentile landing below the 40th.&lt;/p&gt;

&lt;p&gt;Swapping in your own data is the boring part, which is the point:&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;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly_demand.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parse_dates&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;week&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a 52-week forecast with prediction intervals, from a CSV, with no training.&lt;/p&gt;




&lt;h2&gt;
  
  
  What changed in 3.0
&lt;/h2&gt;

&lt;p&gt;TimesFM 3.0 landed in late August 2026, and it is a bigger jump than the version number suggests.&lt;/p&gt;

&lt;p&gt;Every checkpoint through 2.5 was strictly &lt;strong&gt;univariate&lt;/strong&gt;. One series, its own history, nothing else. That is a real limitation, because most forecasting problems in the wild are not like that. If you are forecasting ice cream sales, past sales alone miss the picture: related product sales matter, foot traffic matters, and crucially, the promotion you already scheduled for next Tuesday matters.&lt;/p&gt;

&lt;p&gt;3.0 is natively multivariate. It brings three things that were previously awkward or impossible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple targets.&lt;/strong&gt; Forecast several related series jointly and let the model use the correlations between them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Past covariates.&lt;/strong&gt; Features you only know historically, like last month's foot traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Past-future covariates.&lt;/strong&gt; Features you know in advance, like scheduled promotions, holidays, or a weather forecast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the interesting capability. The model learns the promotion-to-sales relationship from your historical context, then applies it to future days where you have a promotion planned. A univariate model just projects the weekly pattern forward and misses the bump entirely.&lt;/p&gt;

&lt;p&gt;Architecturally, 3.0 does this with alternating attention. Tokens attend &lt;strong&gt;horizontally&lt;/strong&gt; across time (strictly causal, so no leakage from the future), then &lt;strong&gt;vertically&lt;/strong&gt; across series at each time step, so the model can learn how a spike in one series relates to another. Those two layers alternate through the stack.&lt;/p&gt;

&lt;p&gt;It also stopped decoding autoregressively. Earlier versions generated one patch at a time, which meant latency and compounding errors over long horizons. 3.0 appends masked placeholder tokens for the whole future window and fills them all in a single forward pass.&lt;/p&gt;

&lt;p&gt;The 3.0 API is different from 2.5, so this is not a drop-in upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;timesfm3&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TimesFM3Evaluator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ModelConfig&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ModelConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;checkpoint_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google/timesfm-3.0-pytorch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;per_core_batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&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;forecaster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TimesFM3Evaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;context_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;horizon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;

&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;past_only_cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;past_future_cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_len&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;horizon&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;forecaster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict_batch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;horizon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;horizon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;past_only_covariates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;past_only_cov&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;past_future_covariates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;past_future_cov&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;return_quantiles&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="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;outputs&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;forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;    &lt;span class="c1"&gt;# (3, 24)
&lt;/span&gt;&lt;span class="n"&gt;outputs&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;quantiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;   &lt;span class="c1"&gt;# (3, 24, 9)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the shape of &lt;code&gt;past_future_cov&lt;/code&gt;: it spans context &lt;strong&gt;plus&lt;/strong&gt; horizon, because you are telling the model about events that have not happened yet.&lt;/p&gt;

&lt;p&gt;On benchmarks, Google reports 3.0 taking the top average rank among pretrained foundation models on GIFT-Eval, fev-bench, and the TIME leaderboard, on both point and probabilistic metrics, against competitors including Chronos-2 and the Toto 2.0 family. Notably, it wins even in univariate mode, before you give it any covariates at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  The catch nobody mentions in the benchmark charts
&lt;/h2&gt;

&lt;p&gt;Read this part before you plan a sprint around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 3.0 weights are not open for commercial use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source code in the repo is Apache-2.0, and model weights &lt;strong&gt;up to 2.5&lt;/strong&gt; are Apache-2.0. But the 3.0 pretrained weights ship under a separate &lt;code&gt;timesfm-non-commercial-license-v1.0&lt;/code&gt;, restricted to non-commercial, non-production use. Commercial or production use of the default 3.0 weights is not permitted.&lt;/p&gt;

&lt;p&gt;So the practical situation right now is:&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;TimesFM 2.5&lt;/th&gt;
&lt;th&gt;TimesFM 3.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Parameters&lt;/td&gt;
&lt;td&gt;200M&lt;/td&gt;
&lt;td&gt;330M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context length&lt;/td&gt;
&lt;td&gt;up to 16k&lt;/td&gt;
&lt;td&gt;patch-based, 32-step patches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multivariate&lt;/td&gt;
&lt;td&gt;No (XReg covariates bolted on)&lt;/td&gt;
&lt;td&gt;Yes, native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Future-known covariates&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decoding&lt;/td&gt;
&lt;td&gt;Autoregressive per patch&lt;/td&gt;
&lt;td&gt;Single forward pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weights license&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;Non-commercial only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The best model is the one you probably cannot ship. The one you can ship is a version behind. That may change, and Google has said BigQuery integration for 3.0 is coming, but plan against what is true today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is it worth a try?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if any of these describe you:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have a lot of series and no time to model each one. This is the killer use case. Tuning ARIMA for one series is fine; doing it for 5,000 SKUs is not a job anyone wants. Zero-shot inference across a batch is a single call.&lt;/p&gt;

&lt;p&gt;You need a baseline yesterday. Even if you eventually build something bespoke, having a credible forecast in twenty minutes tells you whether the problem is hard and what score you need to beat.&lt;/p&gt;

&lt;p&gt;You want uncertainty without extra work. Quantiles come out of the box.&lt;/p&gt;

&lt;p&gt;You are already on Google Cloud. TimesFM is wired into BigQuery ML behind an &lt;code&gt;AI.FORECAST&lt;/code&gt; SQL call, into Connected Sheets, and into Vertex Model Garden. If your data already lives in BigQuery, forecasting becomes a query rather than a project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be skeptical if:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have one series and years of clean history. A well-tuned domain model with your actual business logic baked in will often beat a general-purpose one. Foundation models win on breadth, not on any single well-understood problem.&lt;/p&gt;

&lt;p&gt;Your data is genuinely weird. Sparse intermittent demand, hard structural breaks, series driven mostly by exogenous shocks the model cannot see. Zero-shot means the model brings priors from other people's data, and sometimes those priors are simply wrong for you.&lt;/p&gt;

&lt;p&gt;You need commercial deployment of the newest model. See the license section above.&lt;/p&gt;

&lt;p&gt;You need CPU-cheap inference at scale. It is a 200M to 330M parameter transformer. That is small next to an LLM but not free, and the examples assume a GPU.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo and examples: &lt;a href="https://github.com/google-research/timesfm" rel="noopener noreferrer"&gt;github.com/google-research/timesfm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One note: this is a research release, not an officially supported Google product. Treat it accordingly in anything load-bearing.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>ai</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Omarchy: an opinionated Arch install that ships someone else's taste</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:14:53 +0000</pubDate>
      <link>https://dev.to/arshtechpro/omarchy-an-opinionated-arch-install-that-ships-someone-elses-taste-2p3g</link>
      <guid>https://dev.to/arshtechpro/omarchy-an-opinionated-arch-install-that-ships-someone-elses-taste-2p3g</guid>
      <description>&lt;p&gt;Most Linux distributions hand you a foundation and a shrug. Omarchy hands you a finished desk.&lt;/p&gt;

&lt;p&gt;It is an omakase Linux distribution based on Arch, the tiling window manager Hyprland, and the desktop construction-kit Quickshell. "Omakase" is the sushi term: you don't order, the chef decides. In this case the chef is DHH, and the distribution is the answer to a specific question — what if someone spent a year making all the tiling-WM decisions for you, and you just used the result?&lt;/p&gt;

&lt;p&gt;The project is &lt;a href="https://github.com/basecamp/omarchy" rel="noopener noreferrer"&gt;MIT licensed&lt;/a&gt;, incubated at 37signals, and is now on version 4.x.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;Strip the branding and Omarchy is three things stacked:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An installer.&lt;/strong&gt; An ISO that provisions Arch with full-disk encryption, sane partitioning, and a working graphical session in a few minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A configuration layer.&lt;/strong&gt; Hyprland, Quickshell, a top bar, a theming system, a unified keybinding scheme, and dotfiles for tmux, Neovim, and your shell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A curated software set.&lt;/strong&gt; Neovim, Chromium, Obsidian, LibreOffice, Kdenlive, OBS Studio, and more.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repo itself is 82% shell, 8% Lua, sitting at around 4,300 commits. That composition tells you the honest truth about the project: it is a very well-maintained pile of install scripts and config files, not a novel operating system. That is not a criticism. It's the reason you can read it, fork it, and steal from it.&lt;/p&gt;

&lt;p&gt;The framing in the manual is explicit about what it's optimizing for: a beautiful system is a motivating system, and productivity has always been downstream from motivation. Whether you buy that argument will largely determine whether this distribution is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;You download the ISO, write it to a USB stick, boot off it, and answer a few configuration questions. It can be done in under a minute on the fastest modern machines, but it shouldn't take more than 5 minutes even on an older computer.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Secure Boot has to go.&lt;/strong&gt; You must turn off Secure Boot and/or TPM in the BIOS to be able to install Omarchy. If your machine is corporate-managed or you dual-boot Windows with BitLocker on, plan for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption is the default and it changes your keyboard requirements.&lt;/strong&gt; The installation defaults to full encryption, and the full-disk encryption won't allow you to enter the password from a Bluetooth keyboard at startup. You need a wired or 2.4GHz keyboard for the boot prompt. You can hit Ctrl + C on the disk formatting confirmation to switch to an encryption-less installation if you're building a throwaway box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can dual boot.&lt;/strong&gt; You can choose between a full-disk install, which takes over the entire drive, or a free-space install, which puts Omarchy in the unallocated space on a drive. The full-disk option will wipe the selected drive, so back up first.&lt;/p&gt;

&lt;p&gt;Two installer details I didn't expect and think are genuinely well designed:&lt;/p&gt;

&lt;p&gt;If you're setting up a machine for someone else, hit Ctrl + C on the very first screen and Omarchy will prepare the machine for another owner instead. The system installs right away, but keyboard layout, username, and password are deferred until first boot, and the password the new owner picks becomes the encryption password.&lt;/p&gt;

&lt;p&gt;The ISO can also install completely on its own — no keyboard, no wizard — when it's handed its configuration on a second drive, which is how you treat Omarchy as a base image for VMs and fleet machines.&lt;/p&gt;

&lt;p&gt;That second one is the part most reviews skip, and it's the one that makes this interesting beyond a personal laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The keyboard-first part
&lt;/h2&gt;

&lt;p&gt;Omarchy is a tiling window manager setup, so almost everything is a binding. &lt;code&gt;Super + K&lt;/code&gt; shows you all of them, which is the only hotkey you actually need to memorize on day one.&lt;/p&gt;

&lt;p&gt;A representative slice:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hotkey&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Omarchy menu (apps and everything else)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Return&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Shift + Return&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Shift + N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Neovim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Arrow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Move focus in that direction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Shift + Arrow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Swap window in that direction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + 1/2/3/4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jump to workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + F&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle tiling/floating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Super + Ctrl + Shift + Space&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pick a new theme&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one I want to call out separately is the clipboard. On most Linux desktops you need &lt;code&gt;Ctrl + Shift + C/V&lt;/code&gt; in the terminal and &lt;code&gt;Ctrl + C/V&lt;/code&gt; everywhere else. Omarchy binds &lt;code&gt;Super + C&lt;/code&gt;, &lt;code&gt;Super + X&lt;/code&gt;, and &lt;code&gt;Super + V&lt;/code&gt; and they work in both places. It is a small thing that removes a daily papercut, and it is a good example of what "opinionated" buys you: someone noticed the inconsistency and just fixed it globally.&lt;/p&gt;

&lt;p&gt;Bindings live in &lt;code&gt;~/.config/hypr/bindings.lua&lt;/code&gt;. Lua config rather than a bespoke DSL, which means real conditionals and loops if you want them.&lt;/p&gt;

&lt;p&gt;There is also a tmux layer with its own binding set (&lt;code&gt;Super + Alt + K&lt;/code&gt; to view), including layout helpers like &lt;code&gt;tdl &amp;lt;ai&amp;gt;&lt;/code&gt; to spin up an editor + AI + terminal pane arrangement in one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-workstation angle
&lt;/h2&gt;

&lt;p&gt;This is where Omarchy 4 diverges from being "a nice Hyprland config." A few of the defaults are built around agentic coding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Super + Ctrl + Return&lt;/code&gt; opens Herdr, an agent manager, with its own binding set under &lt;code&gt;Super + Ctrl + K&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Super + Shift + Ctrl + A&lt;/code&gt; opens an agent picker&lt;/li&gt;
&lt;li&gt;The tmux layout functions take an AI argument as a first-class parameter&lt;/li&gt;
&lt;li&gt;Dictation is a first-class install option, with push-to-talk on &lt;code&gt;F9&lt;/code&gt; and toggle on &lt;code&gt;Super + Ctrl + X&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Super + Ctrl + Print Screen&lt;/code&gt; does OCR text extraction from anything on screen straight to the clipboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run coding agents in terminals all day, the shape of this system was designed by someone doing the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The maintenance question
&lt;/h2&gt;

&lt;p&gt;The usual worry with a one-person opinionated distribution is what happens when that person moves on. Omarchy just answered it with money: the Omacom Foundation was incorporated as a nonprofit to hold the trademarks, fund the infrastructure, promote the work, and support the open-source projects and developers Omarchy depends on. Eight founding patrons contributed $1 million each, including Tobi Lütke, Patrick Collison, Michael Dell, Jack Dorsey, Matthew Prince, Brendan Iribe, and Jason Fried, with a further $2 million added from Drew Houston and Peter Steinberger.&lt;/p&gt;

&lt;p&gt;Ten million dollars does not guarantee good software. What it does buy is runway, infrastructure, and a reason to believe the ISO will still be built in three years. For a distribution you'd put on a work machine, that matters more than any feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'd push back
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It is Arch underneath, with everything that implies.&lt;/strong&gt; Rolling release, no LTS, and when something breaks mid-week you are debugging Arch. Omarchy ships system snapshots to soften this, but the failure modes are Arch's failure modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secure Boot off is a real cost for some people.&lt;/strong&gt; If your employer requires it, or you want measured boot as part of your threat model, this is a non-starter and no amount of theming fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a try?
&lt;/h2&gt;

&lt;p&gt;It depends almost entirely on which of these you are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it if&lt;/strong&gt; you already want to live in a terminal and a tiling WM, and the thing stopping you is the two weekends of config work. That is precisely the cost Omarchy eliminates. You get someone's finished, coherent, actively maintained setup instead of your own half-finished one, and you can read the shell scripts to understand every decision it made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it if&lt;/strong&gt; you want a reproducible dev machine image. The unattended install path is the most underrated feature here. A pinned ISO plus a config drive gets you identical, encrypted, fully-provisioned workstations across a team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip it if&lt;/strong&gt; you need Secure Boot, need an LTS release cadence, or do work that is fundamentally mouse-driven and GUI-heavy. Skip it if you enjoy building your own environment, because Omarchy's entire value proposition is taking that job away from you.&lt;/p&gt;




&lt;p&gt;Repo: &lt;a href="https://github.com/basecamp/omarchy" rel="noopener noreferrer"&gt;github.com/basecamp/omarchy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Ponytail: the AI coding skill that makes your agent write less code</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:12:26 +0000</pubDate>
      <link>https://dev.to/arshtechpro/ponytail-the-ai-coding-skill-that-makes-your-agent-write-less-code-29l3</link>
      <guid>https://dev.to/arshtechpro/ponytail-the-ai-coding-skill-that-makes-your-agent-write-less-code-29l3</guid>
      <description>&lt;p&gt;You ask your AI agent for a date picker.&lt;/p&gt;

&lt;p&gt;It installs flatpickr, writes a wrapper component, adds a stylesheet, sets up a theme override, and then starts explaining timezone handling to you. Four hundred lines later you have a date picker.&lt;/p&gt;

&lt;p&gt;The senior dev sitting two desks over would have written this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/DietrichGebert/ponytail" rel="noopener noreferrer"&gt;Ponytail&lt;/a&gt; is an open-source skill/plugin that tries to put that person inside your coding agent. It works with Claude Code, Codex, Cursor, Gemini CLI, Copilot, OpenCode, Windsurf, Cline, and a long list of others. MIT licensed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea in one screen
&lt;/h2&gt;

&lt;p&gt;Ponytail is not a tool, a library, or a model. It is a ruleset that gets injected into your agent's context every turn. The core of it is a ladder. Before writing any code, the agent stops at the first rung that holds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Does this need to exist?   -&amp;gt; no: skip it (YAGNI)
2. Already in this codebase?  -&amp;gt; reuse it, don't rewrite
3. Stdlib does it?            -&amp;gt; use it
4. Native platform feature?   -&amp;gt; use it
5. Installed dependency?      -&amp;gt; use it
6. One line?                  -&amp;gt; one line
7. Only then: the minimum that works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's basically it. Everything else in the repo is packaging: adapters for twenty-odd agents, a few slash commands, and a benchmark harness.&lt;/p&gt;

&lt;p&gt;Two things about the ladder that matter more than they look:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It runs after understanding, not instead of it.&lt;/strong&gt; The rules tell the agent to read the code the change touches and trace the actual flow before picking a rung. Lazy about the solution, not about reading the codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation is off the table.&lt;/strong&gt; The rules explicitly protect trust-boundary validation, error handling, security, and accessibility. The stated goal is not "fewest tokens." It is "write only what the task needs." The code comes out small because the extra was never needed, not because it was golfed down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing it
&lt;/h2&gt;

&lt;p&gt;Pick your agent. For Claude Code, two prompts (they have to be sent separately):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add DietrichGebert/ponytail
&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;/plugin &lt;span class="nb"&gt;install &lt;/span&gt;ponytail@ponytail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex plugin marketplace add DietrichGebert/ponytail
codex plugin add ponytail@ponytail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;copilot plugin marketplace add DietrichGebert/ponytail
copilot plugin &lt;span class="nb"&gt;install &lt;/span&gt;ponytail@ponytail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gemini extensions &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/DietrichGebert/ponytail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Cursor, Windsurf, Cline, Kiro, Aider, and Copilot Chat there is no plugin layer, so you copy the matching rules file out of the repo (&lt;code&gt;.cursor/rules/&lt;/code&gt;, &lt;code&gt;.windsurf/rules/&lt;/code&gt;, &lt;code&gt;.clinerules/&lt;/code&gt;, and so on). Several agents also auto-load &lt;code&gt;AGENTS.md&lt;/code&gt; from the project root, and the repo ships one, so those work with zero setup from a checkout.&lt;/p&gt;

&lt;p&gt;The Claude Code and Codex plugins run two small Node lifecycle hooks, so &lt;code&gt;node&lt;/code&gt; needs to be on your PATH. If it isn't, the skills still work; you just lose the always-on activation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The commands
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&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;`/ponytail [lite\&lt;/td&gt;
&lt;td&gt;full\&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}&lt;code&gt;/ponytail-review&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reviews your current diff for over-engineering, hands back a delete-list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ponytail-audit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same, but across the whole repo instead of the diff.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ponytail-debt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Collects the &lt;code&gt;ponytail:&lt;/code&gt; shortcuts you deferred into a ledger.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ponytail-gain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shows the measured impact scoreboard.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/ponytail-help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Quick reference.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;/ponytail-review&lt;/code&gt; is the one I'd reach for first even if you never turn on always-on mode. Pointing it at a diff you already wrote is a low-commitment way to see whether the discipline finds anything real in your codebase.&lt;/p&gt;

&lt;p&gt;Commands need a skill-capable host. The instruction-only adapters (Cursor, Windsurf, Cline, Copilot Chat, Kiro) get the always-on ruleset but not the commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers actually say
&lt;/h2&gt;

&lt;p&gt;This is where the project gets more interesting than most GitHub trending entries, and it's worth walking through because the story has a plot twist.&lt;/p&gt;

&lt;p&gt;The original benchmark claimed 80-94% less code. Someone opened &lt;a href="https://github.com/DietrichGebert/ponytail/issues/126" rel="noopener noreferrer"&gt;issue #126&lt;/a&gt; and made four fair criticisms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A single prompt-completion pair is not how agents are used.&lt;/li&gt;
&lt;li&gt;The baseline was a bare chatty model that padded answers with prose and options, so "lines of the answer" was counting commentary, not code.&lt;/li&gt;
&lt;li&gt;"Prefer one-liners" might trade away safety.&lt;/li&gt;
&lt;li&gt;A seven-word prompt might do the same job as a whole skill.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Against that fairer baseline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;vs no-skill baseline&lt;/th&gt;
&lt;th&gt;LOC&lt;/th&gt;
&lt;th&gt;tokens&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;time&lt;/th&gt;
&lt;th&gt;safe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ponytail&lt;/td&gt;
&lt;td&gt;-54%&lt;/td&gt;
&lt;td&gt;-22%&lt;/td&gt;
&lt;td&gt;-20%&lt;/td&gt;
&lt;td&gt;-27%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;caveman (terse prose)&lt;/td&gt;
&lt;td&gt;-20%&lt;/td&gt;
&lt;td&gt;+7%&lt;/td&gt;
&lt;td&gt;+3%&lt;/td&gt;
&lt;td&gt;+2%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"YAGNI + one-liners" prompt&lt;/td&gt;
&lt;td&gt;-33%&lt;/td&gt;
&lt;td&gt;-14%&lt;/td&gt;
&lt;td&gt;-21%&lt;/td&gt;
&lt;td&gt;-30%&lt;/td&gt;
&lt;td&gt;95%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The per-task numbers are more useful than the average:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;task&lt;/th&gt;
&lt;th&gt;baseline&lt;/th&gt;
&lt;th&gt;ponytail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;date picker&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;color picker&lt;/td&gt;
&lt;td&gt;287&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file dropzone&lt;/td&gt;
&lt;td&gt;251&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;multi-step wizard&lt;/td&gt;
&lt;td&gt;571&lt;/td&gt;
&lt;td&gt;312&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;search items by title&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;export items as CSV&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;count user's items&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shape is clear. Where there is an over-build trap, the cut is enormous, because the agent reaches for &lt;code&gt;&amp;lt;input type="date"&amp;gt;&lt;/code&gt; instead of building a component. Where the code is already minimal (backend CRUD), every arm converges and ponytail does nothing. The -54% headline is an average across both kinds of task, so treat it as "huge sometimes, zero other times," not "half your code everywhere."&lt;/p&gt;

&lt;p&gt;The safety tier is the part I found most convincing. Six tasks seed a starter file, ask for one function, and leave the safety requirement implicit the way a real ticket does. The scorer then executes the produced function against adversarial input: path traversal, SQL injection, a forged token, a malformed CSV row.&lt;/p&gt;

&lt;p&gt;On the path-joining task, the bare one-liner prompt wrote the fewest lines (6) and let a &lt;code&gt;../../&lt;/code&gt; filename escape the directory one run in four. Ponytail wrote about 9.5 lines and held 4/4. The extra three lines were the traversal check. That is the whole argument for using a structured ruleset instead of telling your agent "be brief."&lt;/p&gt;

&lt;p&gt;Two more things I'll credit them for. The benchmark writeup has a limitations section that names its own weak points (one model only, n=4, safety is a floor and not a proof). And it documents a contamination bug they found in their own earlier run, where the plugin's SessionStart hook was firing on the baseline arm too, secretly running ponytail against itself. Finding and publishing that is a better trust signal than any number in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'd push back
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's a prompt, not a guarantee.&lt;/strong&gt; Everything here is instructions in a context window. Models drift, ignore rules under load, and behave differently across vendors. Nothing enforces the ladder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gains depend on your agent being bad in a specific way.&lt;/strong&gt; The wins come from an agent that over-builds. If you're on a stronger model, or you already write tight tickets that say "use a native input," a lot of that headroom is already gone. The README itself notes the effect can invert on some reasoning models that spend extra thinking tokens deliberating the rungs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a try?
&lt;/h2&gt;

&lt;p&gt;Yes, with a caveat about which "yes" this is.&lt;/p&gt;

&lt;p&gt;The cost of trying is two commands and a slash command to turn it off. There's no runtime, no dependency in your project, no lock-in, and the uninstall is documented (including a cleanup script for the state it writes outside the plugin folder). At that price, the question is barely worth deliberating.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/DietrichGebert/ponytail" rel="noopener noreferrer"&gt;github.com/DietrichGebert/ponytail&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Benchmark writeup and reproduction steps: &lt;a href="https://github.com/DietrichGebert/ponytail/blob/main/benchmarks/results/2026-06-18-agentic.md" rel="noopener noreferrer"&gt;benchmarks/results/2026-06-18-agentic.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The M5 Ultra Mac Studio: I Did the Math So You Don't Have To</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:40:11 +0000</pubDate>
      <link>https://dev.to/arshtechpro/the-m5-ultra-mac-studio-i-did-the-math-so-you-dont-have-to-2g10</link>
      <guid>https://dev.to/arshtechpro/the-m5-ultra-mac-studio-i-did-the-math-so-you-dont-have-to-2g10</guid>
      <description>&lt;p&gt;Apple announced the new Mac Studio this morning — M5 Max and the first-ever M5 Ultra. Pre-orders are open, units ship September 22, and the headline for our corner of the world is this: &lt;strong&gt;up to 512GB of unified memory at 1.2TB/s, with Neural Accelerators in every GPU core.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Base M5 Ultra: &lt;strong&gt;$5,499&lt;/strong&gt;. The config you actually want: closer to &lt;strong&gt;$10,000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I've spent the morning reading the spec sheet and the pricing page instead of working, so let me save you the trouble. This is not a review — nobody has benchmarked one yet. This is a "should you open your wallet" analysis, aimed squarely at developers who run models locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed (and what's marketing)
&lt;/h2&gt;

&lt;p&gt;Skip the 8K ProRes stream counts. Three things matter if you run models:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Neural Accelerators came to Ultra for the first time.&lt;/strong&gt; The M5 generation puts dedicated matrix-multiply hardware inside &lt;em&gt;each GPU core&lt;/em&gt;. The M5 Max shipped with this in the MacBook Pro; this is the first Ultra chip to get it. Apple claims up to 4.3x the peak AI compute of M3 Ultra, and up to 4x faster LLM prompt processing in LM Studio.&lt;/p&gt;

&lt;p&gt;That prompt-processing number is the one to care about. Time-to-first-token on long contexts has been Apple silicon's most embarrassing weakness. You paste 60k tokens of codebase into a local model on an M3 Ultra and go make coffee. A 4x improvement there changes whether local agentic coding is &lt;em&gt;usable&lt;/em&gt; versus &lt;em&gt;a demo&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Memory bandwidth went to 1.2TB/s&lt;/strong&gt; — 50% up from M3 Ultra's ~819GB/s. For token generation, bandwidth &lt;em&gt;is&lt;/em&gt; the speed limit. More on this below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It's a quad-die chip.&lt;/strong&gt; M5 Ultra is two dual-die M5 Max chips fused with next-gen UltraFusion — over 4.4TB/s of inter-die bandwidth, four dies presenting as one processor. Up to 36 CPU cores (12 "super cores" + 24 performance cores) and an 80-core GPU.&lt;/p&gt;

&lt;p&gt;Also new and genuinely relevant: &lt;strong&gt;Thunderbolt 5 clustering with RDMA&lt;/strong&gt;. You can pool memory across multiple machines; Apple claims a four-Studio cluster hits 3x the inference throughput of a single box. And macOS 27 brings &lt;strong&gt;Core AI&lt;/strong&gt;, a new framework for deploying full-scale LLMs locally, alongside MLX.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pricing trap nobody will put in the headline
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters, from the configurator:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;M5 Max — 32-core GPU, 36GB, 512GB SSD&lt;/td&gt;
&lt;td&gt;$2,499&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M5 Max — 40-core GPU, 48GB&lt;/td&gt;
&lt;td&gt;$3,099&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;M5 Ultra — 30-core CPU, 64-core GPU, 96GB, 1TB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$5,499&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;M5 Ultra — 36-core CPU, 80-core GPU, 96GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$6,799&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ 256GB unified memory&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+$4,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512GB unified memory&lt;/td&gt;
&lt;td&gt;Late October&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The $5,499 machine is not the M5 Ultra you read about in the headlines.&lt;/strong&gt; It's a binned 30-core/64-core part. The full 36-core CPU / 80-core GPU chip starts at $6,799.&lt;/p&gt;

&lt;p&gt;And if you want the memory — the entire reason to buy this machine for AI — 256GB is a &lt;strong&gt;$4,000&lt;/strong&gt; upgrade. That's a real config at &lt;strong&gt;$10,799&lt;/strong&gt; before you touch storage. Storage tops out at 16TB for another $4,000.&lt;/p&gt;

&lt;p&gt;Some context on why: we're in the middle of a brutal DRAM shortage driven by AI datacenter demand. Apple &lt;em&gt;removed&lt;/em&gt; the 512GB option from the M3 Ultra back in March and raised the 256GB upgrade from $1,600 to $2,000. Against that backdrop, the M5 Ultra Mac Studio starting at $5,499 (up from $3,999 for the M3 Ultra at launch, though that model was repriced to $5,299 in June) is less outrageous than it looks. Doesn't make it cheap.&lt;/p&gt;

&lt;p&gt;Also note: 512GB configs don't ship until late October.&lt;/p&gt;

&lt;h2&gt;
  
  
  The only formula you need
&lt;/h2&gt;

&lt;p&gt;Token generation speed on a memory-bound machine is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokens/sec ≈ (memory bandwidth × efficiency) / bytes read per token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a dense model, "bytes read per token" is basically the whole model. So on a 1.2TB/s machine, back-of-envelope ceilings:&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;Size on disk (Q4)&lt;/th&gt;
&lt;th&gt;Theoretical ceiling&lt;/th&gt;
&lt;th&gt;Realistic guess&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;70B dense&lt;/td&gt;
&lt;td&gt;~40GB&lt;/td&gt;
&lt;td&gt;~30 tok/s&lt;/td&gt;
&lt;td&gt;~20–25 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;235B dense&lt;/td&gt;
&lt;td&gt;~130GB&lt;/td&gt;
&lt;td&gt;~9 tok/s&lt;/td&gt;
&lt;td&gt;~6–7 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;671B MoE (~37B active)&lt;/td&gt;
&lt;td&gt;~380GB&lt;/td&gt;
&lt;td&gt;high, but routing-bound&lt;/td&gt;
&lt;td&gt;~15–25 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Treat these as estimates, not benchmarks.&lt;/strong&gt; Real numbers depend on quantization, framework, and whether the model is dense or MoE. Wait for independent testing before you buy on the strength of a table — including mine.&lt;/p&gt;

&lt;p&gt;The strategic takeaway: &lt;strong&gt;MoE models are the killer app for this machine.&lt;/strong&gt; A big sparse model needs enormous memory to &lt;em&gt;hold&lt;/em&gt; it but only reads its active parameters per token. The Mac Studio's weird superpower — huge memory pool, moderate bandwidth relative to a datacenter GPU — maps onto MoE architectures almost perfectly. And the open-weight frontier has been trending sharply toward MoE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versus the alternatives
&lt;/h2&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;Memory&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;The catch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA DGX Spark&lt;/td&gt;
&lt;td&gt;128GB unified&lt;/td&gt;
&lt;td&gt;~$4,699&lt;/td&gt;
&lt;td&gt;~273GB/s real bandwidth — dense 70B decode is &lt;em&gt;slow&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTX PRO 6000 Blackwell&lt;/td&gt;
&lt;td&gt;96GB GDDR7&lt;/td&gt;
&lt;td&gt;~$8,565&lt;/td&gt;
&lt;td&gt;1,792GB/s, actually trains things, needs a whole PC around it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTX 5090&lt;/td&gt;
&lt;td&gt;32GB&lt;/td&gt;
&lt;td&gt;~$4,300+&lt;/td&gt;
&lt;td&gt;Fastest per dollar until your model doesn't fit. Then it's over.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mac Studio M5 Ultra&lt;/td&gt;
&lt;td&gt;96–512GB&lt;/td&gt;
&lt;td&gt;$5,499–$10,799+&lt;/td&gt;
&lt;td&gt;CUDA is not here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest summary: &lt;strong&gt;NVIDIA wins tokens per second, Apple wins gigabytes per dollar.&lt;/strong&gt; Nothing else at any price puts 256GB — soon 512GB — of fast, coherent memory in a box that draws a few hundred watts and makes no noise on your desk.&lt;/p&gt;

&lt;p&gt;That "no noise" thing is not a joke, by the way. A 4-GPU rig in a home office is a space heater with a jet engine attached. This is a small metal cube.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CUDA tax is the real cost.&lt;/strong&gt; Say it out loud before you buy: vLLM's continuous batching, TensorRT-LLM, most fine-tuning tutorials, most inference-server deployment guides, and half the research code on GitHub assume CUDA. MLX is genuinely good now and Core AI looks promising, but you will hit papers with reference implementations you can't run, and you'll be waiting on someone to port things. If your production target is an NVIDIA box, developing on Apple silicon means your dev environment and your deploy environment disagree.&lt;/p&gt;

&lt;p&gt;And for serving concurrent users, it's not close — MLX doesn't match vLLM's batching efficiency. One developer? Great. Twenty concurrent users? Buy the NVIDIA hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The break-even math
&lt;/h2&gt;

&lt;p&gt;The strongest financial argument for local inference is that your API bill never stops.&lt;/p&gt;

&lt;p&gt;Take the $6,799 config. If you're spending &lt;strong&gt;$200/month&lt;/strong&gt; on AI subscriptions and API credits, that's a &lt;strong&gt;34-month&lt;/strong&gt; payback. At &lt;strong&gt;$500/month&lt;/strong&gt;, it's about &lt;strong&gt;14 months&lt;/strong&gt;. At $1,000/month — a small team, or one person running heavy agentic workloads — you're square in under 7 months, and the machine has residual value after that.&lt;/p&gt;

&lt;p&gt;But be honest about three things this math hides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Electricity and time.&lt;/strong&gt; Not huge on a Mac, but nonzero. And the hours you spend fighting quantization configs are hours you didn't bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depreciation against a moving target.&lt;/strong&gt; Apple is already telegraphing M7 Max and M7 Ultra for 2028 with a large AI-performance jump. Hardware you buy today competes against models that get more efficient every quarter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The frontier gap.&lt;/strong&gt; This is the big one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The thing most "run LLMs locally!" posts won't tell you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local open-weight models are not the frontier models you're currently using.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your daily driver is a top-tier hosted model for serious coding work, a Mac Studio does not replace it. It gives you a very good open-weight model that runs privately and for free-at-the-margin. Those are different products. The gap has narrowed a lot and keeps narrowing — but if you buy this expecting to cancel your subscriptions and notice no difference, you will be disappointed.&lt;/p&gt;

&lt;p&gt;Where local genuinely wins, and wins decisively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data that legally cannot leave your building.&lt;/strong&gt; Health records, client code under NDA, regulated financial data. This isn't a preference, it's a compliance requirement, and it's the single best reason to buy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volume grunt work.&lt;/strong&gt; Classifying 10 million documents, generating synthetic training data, bulk embeddings. Work where a good-enough model running 24/7 at zero marginal cost beats a great model with a meter running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent loops that burn tokens like oxygen.&lt;/strong&gt; Long-running agents that iterate hundreds of times. Cost per iteration going to zero changes what you're willing to attempt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tuning small models on your own data&lt;/strong&gt;, where the resulting specialist beats a generalist at your narrow task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Airplanes, and not being at the mercy of someone's rate limits.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So: who should actually buy this?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Buy the M5 Ultra if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy or compliance forces inference on-prem, and you'd otherwise be quoting datacenter hardware&lt;/li&gt;
&lt;li&gt;You're doing local inference research where model &lt;em&gt;size&lt;/em&gt; is the binding constraint&lt;/li&gt;
&lt;li&gt;You're a team of 2–5 sharing one inference box (and the alternative is a $30k server)&lt;/li&gt;
&lt;li&gt;You genuinely spend $500+/month on inference already and the workload is steady&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Buy the M5 Max (40-core, $3,099) instead if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're a working developer who wants strong local inference &lt;em&gt;and&lt;/em&gt; a fast build machine&lt;/li&gt;
&lt;li&gt;128GB is enough for your models — for most people running 30B-class models with real context, it is&lt;/li&gt;
&lt;li&gt;You want the same Neural Accelerator architecture without the Ultra tax&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly? For most developers reading this, &lt;strong&gt;the M5 Max is the right machine and the M5 Ultra is ego.&lt;/strong&gt; Compile times, Docker, a local model for autocomplete and refactoring — 128GB handles that comfortably at half the price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't buy either if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want maximum tokens/sec per dollar → RTX 5090&lt;/li&gt;
&lt;li&gt;You need CUDA for training or production parity → NVIDIA, no contest&lt;/li&gt;
&lt;li&gt;You're serving many concurrent users → vLLM on NVIDIA&lt;/li&gt;
&lt;li&gt;Your API spend is under $100/month → the math doesn't work; it won't work.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>mobile</category>
      <category>agents</category>
    </item>
    <item>
      <title>DeviceCheck and App Attest: Stopping Fraud in iOS Apps</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:57:39 +0000</pubDate>
      <link>https://dev.to/arshtechpro/devicecheck-and-app-attest-stopping-fraud-in-ios-apps-472e</link>
      <guid>https://dev.to/arshtechpro/devicecheck-and-app-attest-stopping-fraud-in-ios-apps-472e</guid>
      <description>&lt;p&gt;Your API has no idea what is calling it.&lt;/p&gt;

&lt;p&gt;A request arriving at &lt;code&gt;POST /api/redeem&lt;/code&gt; looks identical whether it came from your app on a real iPhone, from a modified build running on a jailbroken device, or from a Python script someone wrote after reading your traffic in Charles Proxy. HTTPS proves the connection is encrypted. It proves nothing about the client.&lt;/p&gt;

&lt;p&gt;Apple gives you two frameworks to close that gap. This post covers what each one actually guarantees, what it does not, and how to implement both without the mistakes that show up in most tutorials.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;What DeviceCheck actually does&lt;/li&gt;
&lt;li&gt;What App Attest actually does&lt;/li&gt;
&lt;li&gt;Choosing between them&lt;/li&gt;
&lt;li&gt;Implementing DeviceCheck&lt;/li&gt;
&lt;li&gt;Implementing App Attest&lt;/li&gt;
&lt;li&gt;Fraud scenarios&lt;/li&gt;
&lt;li&gt;Mistakes to avoid&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What DeviceCheck actually does
&lt;/h2&gt;

&lt;p&gt;DeviceCheck (iOS 11+) does two things, and it is worth being precise because it is routinely oversold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One.&lt;/strong&gt; It confirms a token came from a genuine Apple device that has your app installed, where your app is tied to your developer account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two.&lt;/strong&gt; It gives you two bits of storage per device, per developer, held on Apple's servers. Two bits means four states. It survives app deletion and reinstall, which is the entire point.&lt;/p&gt;

&lt;p&gt;That's it. Read the list again for what is &lt;em&gt;not&lt;/em&gt; on it. DeviceCheck does not detect jailbreaks. It does not tell you whether your app binary was modified. It does not identify the user. It is not authentication.&lt;/p&gt;

&lt;p&gt;The two-bit storage is the interesting part. Because it persists across reinstalls, it answers questions that a locally-stored flag cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has this device already claimed the new-user promo?&lt;/li&gt;
&lt;li&gt;Has this device been flagged for chargeback fraud?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A user who deletes your app, reinstalls it, and creates a fresh account still hits the same two bits.&lt;/p&gt;

&lt;p&gt;One gotcha: the device token from &lt;code&gt;generateToken&lt;/code&gt; is &lt;strong&gt;single-use&lt;/strong&gt;. &lt;cite&gt;You should treat the token you receive in the completion block as single-use — although it remains valid long enough to retry a specific request, you should not use it multiple times.&lt;/cite&gt; Generate a fresh one per request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What App Attest actually does
&lt;/h2&gt;

&lt;p&gt;App Attest (iOS 14+) answers a harder question: &lt;em&gt;is this specific request coming from an unmodified copy of my app?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It works by generating a key pair in the device's Secure Enclave. The private key never leaves the hardware and cannot be extracted. Apple then issues a certificate chain vouching that this key belongs to a legitimate instance of your app, with your Team ID and Bundle ID baked in.&lt;/p&gt;

&lt;p&gt;Once that key is attested, your app signs each sensitive request with it. Your server verifies the signature. A tampered app cannot produce valid signatures, because it cannot get its own key attested.&lt;/p&gt;

&lt;p&gt;The flow splits into two phases, and conflating them is the most common implementation error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attestation&lt;/strong&gt; happens once per key install. Apple's servers are involved. It is relatively expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assertion&lt;/strong&gt; happens per request afterward. It is cheap. Critically, &lt;cite&gt;the assertion flow is simpler than attestation, as the Apple servers are no longer involved&lt;/cite&gt; — your server does the verification with the public key it stored during attestation.&lt;/p&gt;

&lt;p&gt;There is no Apple REST endpoint that validates attestations or assertions for you. &lt;cite&gt;Your server, not the app, must validate attestations — a compromised client cannot be trusted to validate its own integrity.&lt;/cite&gt; You parse the CBOR attestation object and check the certificate chain against Apple's App Attest root CA yourself, or you use a library that does it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing between them
&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;DeviceCheck&lt;/th&gt;
&lt;th&gt;App Attest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Confirms genuine Apple device&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;Confirms app binary unmodified&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;Persistent per-device state&lt;/td&gt;
&lt;td&gt;Yes (2 bits)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secure Enclave keys&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;Replay protection&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (counter + nonce)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum iOS&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple servers in the loop&lt;/td&gt;
&lt;td&gt;Every call&lt;/td&gt;
&lt;td&gt;Attestation only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server work&lt;/td&gt;
&lt;td&gt;Call Apple's API&lt;/td&gt;
&lt;td&gt;Verify crypto yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They solve different problems and compose well. App Attest tells you the request is authentic. DeviceCheck remembers that this device already burned its free trial. Plenty of production apps run both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing DeviceCheck
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;DeviceCheck&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchDeviceToken&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;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;guard&lt;/span&gt; &lt;span class="kt"&gt;DCDevice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSupported&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;DeviceCheckError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsupported&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;DCDevice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateToken&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;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64EncodedString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;isSupported&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; on the Simulator. Plan for that in your dev workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server
&lt;/h3&gt;

&lt;p&gt;You need a &lt;code&gt;.p8&lt;/code&gt; key with DeviceCheck enabled from the Apple Developer portal, and you sign an ES256 JWT with it. &lt;cite&gt;The HTTP header field in each request must contain the authentication key you receive from Apple in a JSON web token.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Three endpoints, all POST:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;validate_device_token&lt;/code&gt; — is this a real device?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query_two_bits&lt;/code&gt; — read the stored state&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update_two_bits&lt;/code&gt; — write it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Swap &lt;code&gt;api.devicecheck.apple.com&lt;/code&gt; for &lt;code&gt;api.development.devicecheck.apple.com&lt;/code&gt; in dev.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;appleJWT&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="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;PRIVATE_KEY_P8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ES256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// 10-char Key ID&lt;/span&gt;
    &lt;span class="na"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TEAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// 10-char Team ID&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queryBits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deviceToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/query_two_bits`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;appleJWT&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;device_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deviceToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;// milliseconds, not seconds&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// A device with no bits set yet returns a plain-text body,&lt;/span&gt;
  &lt;span class="c1"&gt;// not JSON. Handle it before parsing.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to find bit state&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bit0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bit1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isNew&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;isNew&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things bite people here: &lt;code&gt;timestamp&lt;/code&gt; is in &lt;strong&gt;milliseconds&lt;/strong&gt;, and &lt;code&gt;transaction_id&lt;/code&gt; must be unique per request. A brand-new device returns a non-JSON body, so parse defensively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing App Attest
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: attestation (once)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;DeviceCheck&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;CryptoKit&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;attest&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;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DCAppAttestService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSupported&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="kt"&gt;AttestError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unsupported&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Server issues a one-time random challenge.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchChallenge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Create a Secure Enclave key. Persist the ID in Keychain.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;keyId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Keychain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"appattest.keyId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Hash the challenge. This must be SHA256.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;clientDataHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SHA256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Attest. Apple's servers participate in this step.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;attestation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attestKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;clientDataHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clientDataHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 5. Ship it to your server for verification.&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAttestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attestation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;base64EncodedString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist that &lt;code&gt;keyId&lt;/code&gt;. If you lose it you must attest a fresh key, and &lt;cite&gt;each call to &lt;code&gt;generateKey&lt;/code&gt; returns a new keyId referring to a unique key pair&lt;/cite&gt; — the old ones do not get replaced.&lt;/p&gt;

&lt;p&gt;Your server then verifies the attestation object. Do not write the CBOR and X.509 parsing yourself unless you have a reason to; use &lt;code&gt;node-app-attest&lt;/code&gt;, &lt;code&gt;appattest-checker-node&lt;/code&gt;, or &lt;code&gt;veehaitch/devicecheck-appattest&lt;/code&gt; for JVM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyAttestation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-app-attest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receipt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifyAttestation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storedChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// the one YOU issued&lt;/span&gt;
  &lt;span class="na"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bundleIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;com.example.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;teamIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ABCDE12345&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;allowDevelopmentEnvironment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isProd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Store publicKey for assertions, receipt for the fraud metric,&lt;/span&gt;
&lt;span class="c1"&gt;// and signCount starting at 0.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveAttestation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receipt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;signCount&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burn the challenge after use. A reused challenge defeats the whole mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: assertion (every sensitive request)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;signedRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;keyId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Keychain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"appattest.keyId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchChallenge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"challenge"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bodyData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;JSONSerialization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;withJSONObject&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="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;clientDataHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SHA256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bodyData&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;assertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;DCAppAttestService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateAssertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;clientDataHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clientDataHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Send bodyData AND the assertion AND the keyId.&lt;/span&gt;
    &lt;span class="c1"&gt;// The assertion object does not contain the keyId.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bodyData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyId&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;Server side, no Apple call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signCount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifyAssertion&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bundleIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;com.example.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;teamIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ABCDE12345&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// The counter must strictly increase. A repeat or a&lt;/span&gt;
&lt;span class="c1"&gt;// decrease means someone is replaying a captured assertion.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signCount&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;replay detected&lt;/span&gt;&lt;span class="dl"&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;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateSignCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;cite&gt;The authenticator data includes an ever-increasing counter&lt;/cite&gt; — checking it is what gives you replay protection, and skipping the check throws that away.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fraud metric
&lt;/h3&gt;

&lt;p&gt;That &lt;code&gt;receipt&lt;/code&gt; you stored is worth something. &lt;cite&gt;The fraud metric is an approximate 30-day count of unique attested keys associated with your app on a particular device, retrieved by your server from the App Attest data server using a stored attestation receipt.&lt;/cite&gt; A device generating hundreds of keys is very likely an attestation broker feeding a bot farm. A device with two or three has probably just reinstalled the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fraud scenarios
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bot traffic and credential stuffing.&lt;/strong&gt; A script hitting your API cannot produce an assertion, because it has no Secure Enclave key and cannot get one attested. App Attest turns "rate limit the abuse" into "the abuse cannot reach the endpoint."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simulator and emulator abuse.&lt;/strong&gt; Both frameworks report &lt;code&gt;isSupported == false&lt;/code&gt; on the Simulator. Free-tier farming through simulators stops working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeat promo abuse.&lt;/strong&gt; DeviceCheck's bits outlive app deletion. Set bit0 when the promo is claimed; check it before granting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modified builds.&lt;/strong&gt; Someone patches your IPA to skip the receipt validation and sideloads it. The modified app's key will not attest, because the attestation includes a hash of your app's identity that Apple signs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account takeover.&lt;/strong&gt; Attestation gives you a stable per-install identity independent of credentials. Stolen password plus unrecognized attested key equals a good reason to demand a second factor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes to avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Treating App Attest as a boolean.&lt;/strong&gt; &lt;cite&gt;Do not reject every new key for an existing user — app reinstall and device restore can legitimately invalidate a key and require key rotation.&lt;/cite&gt; Legitimate users rotate keys. Feed the signal into a risk score; do not hard-block on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validating on the client.&lt;/strong&gt; If the app decides whether its own attestation is valid, an attacker patches out the check. Verification belongs on the server, always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusing challenges.&lt;/strong&gt; One challenge, one use, short expiry, stored server-side. Otherwise a captured assertion replays forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping the counter check.&lt;/strong&gt; It is three lines and it is the difference between replay protection and none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attesting during checkout.&lt;/strong&gt; &lt;cite&gt;Perform attestation outside critical user flows when possible, retry later on failures, and use exponential backoff instead of hard-coded retry loops.&lt;/cite&gt; Attestation can fail transiently. Do it at launch or during onboarding, not while the user is trying to pay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shipping the &lt;code&gt;.p8&lt;/code&gt; in the app.&lt;/strong&gt; Your DeviceCheck key lives on the server. Nowhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assuming this replaces everything else.&lt;/strong&gt; Attestation raises the cost of attack; it does not make it infinite. A determined attacker with a jailbroken device and Frida can hook the call and relay valid assertions from a script. Keep your rate limiting, your behavioral analysis, and your server-side validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity" rel="noopener noreferrer"&gt;App Attest documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/devicecheck/accessing_and_modifying_per-device_data" rel="noopener noreferrer"&gt;Accessing and modifying per-device data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>security</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How Dopamine Works: The Architecture of a Modern iOS Jailbreak</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:06:56 +0000</pubDate>
      <link>https://dev.to/arshtechpro/how-dopamine-works-the-architecture-of-a-modern-ios-jailbreak-2hj3</link>
      <guid>https://dev.to/arshtechpro/how-dopamine-works-the-architecture-of-a-modern-ios-jailbreak-2hj3</guid>
      <description>&lt;p&gt;Most developers will never jailbreak a phone. That is fine. This article is not a how-to, and there is no install guide here.&lt;/p&gt;

&lt;p&gt;What makes &lt;a href="https://github.com/opa334/Dopamine" rel="noopener noreferrer"&gt;Dopamine&lt;/a&gt; worth reading as an engineer is the constraint it operates under: it has to run a full package ecosystem on an operating system that was explicitly designed to make that impossible, without modifying a single byte of the system partition, and it has to survive every process launch on the device.&lt;/p&gt;

&lt;p&gt;Dopamine is a rootless, semi-untethered jailbreak by opa334 and évelyne, written mostly in C and Objective-C. Version support depends heavily on chip and iOS version, so check the README rather than trusting any number in a blog post.&lt;/p&gt;

&lt;p&gt;Let's walk the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules of the game
&lt;/h2&gt;

&lt;p&gt;Every design decision downstream falls out of what iOS enforces. Five things matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system volume is sealed.&lt;/strong&gt; Since iOS 15, the root filesystem is a cryptographically sealed snapshot. You cannot write to &lt;code&gt;/usr/lib&lt;/code&gt; and have the device boot. So the entire jailbreak has to live somewhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code must be signed.&lt;/strong&gt; The kernel refuses to execute pages that do not carry a valid signature chain. There is also a &lt;em&gt;trust cache&lt;/em&gt;, a kernel-side list of hashes that are allowed to run. A tweak you compiled ten seconds ago is in neither.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processes are sandboxed.&lt;/strong&gt; Even as root, a process only sees what its sandbox profile allows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Libraries are validated.&lt;/strong&gt; Library validation means a process will typically only load libraries signed by the same team as the main binary. That is the single biggest obstacle to loading third-party code into Apple's own processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory is protected below the kernel.&lt;/strong&gt; On modern arm64e chips there are protection layers beneath the kernel itself (PPL, and later SPTM) that guard page tables, plus pointer authentication on function pointers. Kernel read/write alone is no longer enough.&lt;/p&gt;

&lt;p&gt;So: no writable system directory, no way to sign code, no way to load unsigned libraries, and a kernel you cannot fully trust yourself inside. Everything below is a response to one of those five facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers
&lt;/h2&gt;

&lt;p&gt;The repo splits cleanly in two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application/   Objective-C, UIKit. The app you tap. Orchestration, UI, logs.
BaseBin/       C, Mach, assembly. The runtime that lives on the device afterwards.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Application&lt;/code&gt; is the installer and control panel. &lt;code&gt;BaseBin&lt;/code&gt; is the actual jailbreak. Almost everything interesting is in &lt;code&gt;BaseBin&lt;/code&gt;, and it keeps running long after the app is closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: exploits as plugins
&lt;/h2&gt;

&lt;p&gt;The app has a &lt;code&gt;DOExploitManager&lt;/code&gt; that selects an exploit based on the device's chip family and OS build, and a &lt;code&gt;DOJailbreaker&lt;/code&gt; that drives the whole sequence.&lt;/p&gt;

&lt;p&gt;The part worth stealing here is not the exploits, it is the &lt;strong&gt;plugin boundary&lt;/strong&gt;. Each exploit ships as a bundle with an &lt;code&gt;Info.plist&lt;/code&gt; declaring what it supports. The repo has several of them side by side, and the wiki has a page on adding new ones. The orchestration code does not care which one runs. It asks the manager for something compatible with this device, runs it, and gets a set of capabilities back.&lt;/p&gt;

&lt;p&gt;This is a hardware abstraction layer, applied to bugs. When a new technique appears, you add a bundle instead of rewriting the jailbreak. Given how quickly individual entry points get patched, that boundary is the reason the project survived across four-plus years of OS releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: from a bug to a stable primitive
&lt;/h2&gt;

&lt;p&gt;Raw exploitation gives you something awkward and fragile. What the rest of the system wants is a clean interface: read kernel memory, write kernel memory, call a kernel function, mark this page executable.&lt;/p&gt;

&lt;p&gt;That translation lives in &lt;code&gt;libjailbreak&lt;/code&gt;. It also holds a table of kernel structure offsets that vary per Darwin version, because struct layouts change between iOS releases and there are no headers for the ones that matter.&lt;/p&gt;

&lt;p&gt;That table is why version support is enumerated so precisely, and why "it should probably work on the next point release" is never true. A wrong offset is not a bug report, it is a kernel panic.&lt;/p&gt;

&lt;p&gt;Note the layering discipline: exploitation is one module, primitives are another, and every consumer above talks to the primitive API only. A large percentage of the codebase never has to know how privileges were obtained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: the rootless bootstrap
&lt;/h2&gt;

&lt;p&gt;The system volume is sealed, so Dopamine installs into a randomized path under &lt;code&gt;/private/preboot&lt;/code&gt;, and exposes it at &lt;code&gt;/var/jb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have ever built software that must be relocatable, this will look familiar. It is &lt;code&gt;/usr/local&lt;/code&gt; versus &lt;code&gt;/usr&lt;/code&gt;, or a container volume mount versus the base image. Every package is compiled to reference &lt;code&gt;/var/jb/...&lt;/code&gt; instead of &lt;code&gt;/&lt;/code&gt;, the actual location is randomized per install, and the symlink hides that indirection from everything above.&lt;/p&gt;

&lt;p&gt;The environment itself is a Procursus bootstrap: a proper Debian-style userland with dpkg, so packages install through Sileo or Zebra using ordinary &lt;code&gt;.deb&lt;/code&gt; semantics.&lt;/p&gt;

&lt;p&gt;Two things fall out of this design. Restoring the device is mostly a matter of deleting a directory, not repairing a system partition. And system updates do not fight with a modified root. "Rootless" sounds like a limitation; in practice it made jailbreaks dramatically less destructive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4: a capability server inside PID 1
&lt;/h2&gt;

&lt;p&gt;Here is the design decision I find most interesting.&lt;/p&gt;

&lt;p&gt;You have kernel read/write. The naive approach is to hand that to every process that needs it. That is a disaster: any of them can panic the kernel, and every one of them is now a privilege escalation target.&lt;/p&gt;

&lt;p&gt;Dopamine does the opposite. It injects a hook into &lt;code&gt;launchd&lt;/code&gt; (PID 1), and inside it runs &lt;strong&gt;&lt;code&gt;jbserver&lt;/code&gt;&lt;/strong&gt;, a Mach service that owns the privileged primitives. Everything else is a client that sends requests over Mach or XPC via &lt;code&gt;libjailbreak&lt;/code&gt;. Requests are organized into domains, and callers are checked for what they are allowed to ask for.&lt;/p&gt;

&lt;p&gt;That is a broker pattern, straight out of browser sandbox design. One privileged component, a narrow typed API, everyone else unprivileged. The clients cannot corrupt the kernel because they never touch it.&lt;/p&gt;

&lt;p&gt;Putting it inside &lt;code&gt;launchd&lt;/code&gt; also solves persistence: PID 1 never dies while userspace is alive, so the jailbreak state outlives the app entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5: getting into every process
&lt;/h2&gt;

&lt;p&gt;For tweaks to work, code has to load into arbitrary system processes. Two components handle this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;dyldhook&lt;/code&gt;&lt;/strong&gt; patches the dynamic linker itself. It runs before the process's &lt;code&gt;main&lt;/code&gt;, checks the process in with &lt;code&gt;jbserver&lt;/code&gt; to receive its sandbox extensions and environment info, and handles library validation by making sure a library's signature is registered in the trust cache before the kernel evaluates it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;systemhook.dylib&lt;/code&gt;&lt;/strong&gt; is inserted via &lt;code&gt;DYLD_INSERT_LIBRARIES&lt;/code&gt; and does the ongoing work: loading tweaks, and hooking &lt;code&gt;posix_spawn&lt;/code&gt; and &lt;code&gt;execve&lt;/code&gt; so that every child process inherits the injection.&lt;/p&gt;

&lt;p&gt;That last detail is the whole trick. Think &lt;code&gt;LD_PRELOAD&lt;/code&gt;, except it re-preloads itself into everything it spawns. Inject once into PID 1, and the property propagates down the entire process tree by induction. You never have to enumerate processes or race a launch.&lt;/p&gt;

&lt;p&gt;Actual tweak hooking is delegated to ElleKit, an open-source hooking library that replaced the old proprietary Substrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 6: making the system not notice
&lt;/h2&gt;

&lt;p&gt;This is where most of the engineering hours actually went, and it is the least glamorous part.&lt;/p&gt;

&lt;p&gt;Once you modify a running process, the OS starts noticing. &lt;code&gt;csops&lt;/code&gt; reports the process as invalid. On iOS 16 the networking policy layer began checking code signing validity, which meant modified processes silently lost network access. So &lt;code&gt;systemhook&lt;/code&gt; hooks those paths and re-validates.&lt;/p&gt;

&lt;p&gt;On arm64e, &lt;code&gt;fork()&lt;/code&gt; breaks, because the child needs to inherit memory protections and signing state that the kernel will not copy for it. The fix, &lt;code&gt;forkfix&lt;/code&gt;, is a small masterpiece of pragmatism: hook &lt;code&gt;__fork&lt;/code&gt;, use a pipe pair to freeze the child immediately after it appears, have the parent ask &lt;code&gt;jbserver&lt;/code&gt; to apply the necessary fixups to the child PID, then let it continue.&lt;/p&gt;

&lt;p&gt;There is also a jetsam multiplier, because processes carrying a stack of injected tweaks blow through memory limits and get killed.&lt;/p&gt;

&lt;p&gt;None of this is the exciting part of a jailbreak. All of it is why it is usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semi-untethered, and the userspace reboot
&lt;/h2&gt;

&lt;p&gt;Everything above lives in memory. A real reboot wipes it, which is what "semi-untethered" means: the device boots stock, and you reopen the app to re-apply.&lt;/p&gt;

&lt;p&gt;There is a middle option, though. A &lt;strong&gt;userspace reboot&lt;/strong&gt; tears down and restarts userland without a kernel boot, which means the jailbreak state can be re-established without re-running an exploit. Practically, it turns "something broke, reboot and start over" into a thirty-second operation. &lt;code&gt;launchdhook&lt;/code&gt; is what makes it possible, because PID 1 is where the state lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The map
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Dopamine.app
        |
        | selects + runs
        v
  Exploit bundle  ---&amp;gt;  libjailbreak  (kernel primitives, version offsets)
                              |
                              v
                        launchdhook  in PID 1
                              |
                        +-----+-----+
                        |  jbserver |  &amp;lt;---- Mach / XPC ---- every process
                        +-----+-----+
                              |
   /var/jb  ------------------+   trust cache, sandbox extensions, fixups
   (Procursus bootstrap)
                              |
     dyldhook + systemhook injected into each process on spawn
                              |
                        ElleKit -&amp;gt; tweaks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What transfers to normal software
&lt;/h2&gt;

&lt;p&gt;Strip away the iOS specifics and there are four patterns here worth borrowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolate the volatile part behind an interface.&lt;/strong&gt; Exploits are plugins with declared compatibility. When your dependency on the outside world is guaranteed to break, make replacing it a config change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize dangerous capability, distribute access.&lt;/strong&gt; One broker owns the primitive, everyone else gets a narrow API. This is browser sandboxing, syscall filtering, and &lt;code&gt;jbserver&lt;/code&gt;, all the same shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make your install root relocatable and additive.&lt;/strong&gt; Never modify what you do not own. Add a prefix and indirect through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagate through inheritance, not enumeration.&lt;/strong&gt; Hooking spawn beats scanning for processes, in the same way that fixing a base image beats patching running containers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;Jailbreaking is legal in many jurisdictions but not all, it voids your warranty, and the same mechanisms described here are why a jailbroken device is a weaker security boundary than a stock one. Read the code for the engineering. That is where the value is.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobile</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Xcode 27: Everything Developers Need to Know</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 10 Aug 2026 10:27:47 +0000</pubDate>
      <link>https://dev.to/arshtechpro/xcode-27-everything-developers-need-to-know-6lh</link>
      <guid>https://dev.to/arshtechpro/xcode-27-everything-developers-need-to-know-6lh</guid>
      <description>&lt;p&gt;Xcode 27 is in beta right now (beta 4 at the time of writing), and it is a big release. Apple's official release notes are lots of lines of radar numbers and one-line bug fixes, which is great as a reference and terrible as a read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you even run it?
&lt;/h2&gt;

&lt;p&gt;Before anything else, check these three lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Xcode 27 requires a Mac running &lt;strong&gt;macOS Tahoe 26.4 or later&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Xcode 27 &lt;strong&gt;only installs and runs on Apple silicon Macs&lt;/strong&gt;. If you are still on an Intel Mac, this release is the end of the line for you.&lt;/li&gt;
&lt;li&gt;On-device debugging supports &lt;strong&gt;iOS 17+, tvOS 17+, watchOS 10+, and visionOS&lt;/strong&gt;. Older devices are no longer debuggable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It ships with &lt;strong&gt;Swift 6.4&lt;/strong&gt; and the SDKs for iOS 27, iPadOS 27, tvOS 27, watchOS 27, macOS 27, and visionOS 27.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: The breaking changes (read this section)
&lt;/h2&gt;

&lt;p&gt;I am putting these first because they are the parts that turn into a red build log on Monday morning. Everything else is upside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intel is being phased out of your build settings
&lt;/h3&gt;

&lt;p&gt;If a target's minimum deployment target is macOS 27.0 or DriverKit 27.0, it will &lt;strong&gt;no longer build Universal by default&lt;/strong&gt;. The &lt;code&gt;ARCHS_STANDARD&lt;/code&gt; setting drops &lt;code&gt;x86_64&lt;/code&gt; once &lt;code&gt;MACOSX_DEPLOYMENT_TARGET&lt;/code&gt; or &lt;code&gt;DRIVERKIT_DEPLOYMENT_TARGET&lt;/code&gt; is 27.0 or higher.&lt;/p&gt;

&lt;p&gt;If you still ship to Intel Macs, you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep your minimum deployment target below macOS 27.0.&lt;/li&gt;
&lt;li&gt;Explicitly add &lt;code&gt;x86_64&lt;/code&gt; back to the &lt;code&gt;ARCHS&lt;/code&gt; build setting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The macOS 27 SDK can still back-deploy Universal apps down to macOS 12, so this is a default change, not a hard removal.&lt;/p&gt;

&lt;h3&gt;
  
  
  The old linker is gone
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ld64&lt;/code&gt; has been removed and the &lt;code&gt;-ld_classic&lt;/code&gt; flag is no longer supported. If you have that flag lingering in &lt;code&gt;OTHER_LDFLAGS&lt;/code&gt; from some 2023-era workaround, delete it now. This one is a hard failure, not a warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Demand Resources is deprecated
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;NSBundleResourceRequest&lt;/code&gt; and the whole On Demand Resources system are deprecated. The replacement is &lt;strong&gt;Background Assets&lt;/strong&gt;, which got a solid round of improvements this release (more on that below).&lt;/p&gt;

&lt;h3&gt;
  
  
  PreviewProvider is deprecated
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PreviewProvider&lt;/code&gt; and its family of preview modifiers are now deprecated. If you still have the old struct-based previews hanging around, this is your nudge to move to the &lt;code&gt;#Preview&lt;/code&gt; macro.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView_Previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreviewProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ContentView&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;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="cp"&gt;#Preview {&lt;/span&gt;
    &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A real Swift source break
&lt;/h3&gt;

&lt;p&gt;This one is subtle. A computed property that has both an &lt;code&gt;init&lt;/code&gt; accessor and an array or dictionary literal as its initial value will no longer compile &lt;strong&gt;if the getter is declared before the &lt;code&gt;init&lt;/code&gt; accessor&lt;/strong&gt;. This is a known source break from SE-0508.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;S&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;_strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_strings&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;@storageRestrictions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;initializes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_strings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;init&lt;/span&gt; &lt;span class="p"&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;newValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to reorder: declare the &lt;code&gt;init&lt;/code&gt; accessor first, then the getter. Annoying, but a one-line move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate Clang module names now fail
&lt;/h3&gt;

&lt;p&gt;The Swift dependency scanner was optimized to skip redundant header searches, and the tradeoff is that &lt;strong&gt;every Clang module reachable from a single dependency scan must have a unique module name&lt;/strong&gt;. Previously the scanner tolerated duplicates; now it may error out.&lt;/p&gt;

&lt;p&gt;The two situations that trigger this in the real world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An SDK or project that vends the same Clang module name from more than one location on the header search path.&lt;/li&gt;
&lt;li&gt;Vendored third-party sources shipping a &lt;code&gt;module.modulemap&lt;/code&gt; that redeclares a module already in the SDK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you get a mysterious scanning error after upgrading, this is the first thing to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  C++ changes worth knowing
&lt;/h3&gt;

&lt;p&gt;If you have C++ in your app, a few things moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The minimum macOS deployment target for the C++ standard library is now &lt;strong&gt;11.0&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;multimap::find&lt;/code&gt; and &lt;code&gt;multiset::find&lt;/code&gt; no longer guarantee returning an iterator to the first equal element. libc++ used to do this by accident, and the Standard never promised it. Use &lt;code&gt;lower_bound&lt;/code&gt; or &lt;code&gt;equal_range&lt;/code&gt; if you were relying on it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lower_bound&lt;/code&gt; and &lt;code&gt;upper_bound&lt;/code&gt; on &lt;code&gt;std::map&lt;/code&gt; and &lt;code&gt;std::set&lt;/code&gt; behave differently for comparators that are not a strict weak order. Defining &lt;code&gt;_LIBCPP_ENABLE_LEGACY_TREE_LOWER_UPPER_BOUND&lt;/code&gt; gets the old behavior back, but that escape hatch is going away, likely next release.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bitset::operator[]&lt;/code&gt; now returns &lt;code&gt;bool&lt;/code&gt;, which is actually what the Standard says it should do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;std::allocator&lt;/code&gt; is now trivially default-constructible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The upside is significant: associative and unordered containers got up to 11x faster in some functions, several algorithms got up to 3x faster, and &lt;code&gt;distance&lt;/code&gt; on non-random-access segmented iterators improved dramatically. A number of C++ papers landed too, including &lt;code&gt;std::optional&amp;lt;T&amp;amp;&amp;gt;&lt;/code&gt;, &lt;code&gt;zip&lt;/code&gt;, and &lt;code&gt;std::views::indices(n)&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Agentic coding is now a first-class citizen
&lt;/h2&gt;

&lt;p&gt;This is the headline feature, and it is a lot more than a chat box.&lt;/p&gt;

&lt;h3&gt;
  
  
  The assistant moved out of the sidebar
&lt;/h3&gt;

&lt;p&gt;The coding assistant now lives &lt;strong&gt;in the editor area&lt;/strong&gt;, not the navigator, with a redesigned conversation transcript. Artifacts the agent produces (code diffs, plans, SwiftUI preview snapshots) show up next to the transcript, and you can annotate code snippets and plan documents to give targeted inline feedback without leaving the conversation.&lt;/p&gt;

&lt;p&gt;The sidebar is now dedicated purely to organizing conversations: real-time status, unread indicators, drag-and-drop grouping, archiving, renaming, multi-select for bulk actions, and a context menu to open conversations in new tabs, windows, or editor panes.&lt;/p&gt;

&lt;p&gt;There is also a &lt;strong&gt;New Conversation&lt;/strong&gt; button in the toolbar that works from anywhere in Xcode, with a status indicator you can click to jump to whichever conversation needs your attention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plan mode
&lt;/h3&gt;

&lt;p&gt;Planning is now a proper feature rather than a prompting trick. Plans appear as &lt;strong&gt;editable Markdown artifacts&lt;/strong&gt; next to the conversation. You review, annotate, discuss changes, and approve before the agent writes any code.&lt;/p&gt;

&lt;p&gt;This is the workflow I would recommend for anything non-trivial: get the plan right first, then let it execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents can actually run your app
&lt;/h3&gt;

&lt;p&gt;This is the part that changes what agents are useful for. In Xcode 27, agents can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boot simulators, install and launch apps, synthesize touch events, and capture screenshots to verify UI behavior.&lt;/li&gt;
&lt;li&gt;Manipulate the active run state, read and interact with the debugger console.&lt;/li&gt;
&lt;li&gt;List and switch between schemes and run destinations.&lt;/li&gt;
&lt;li&gt;Inspect and modify build settings, compiler flags, entitlements, and Info.plist keys.&lt;/li&gt;
&lt;li&gt;Access project insights such as crashes, disk writes, energy, hangs, and launch issues affecting your shipped app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, an agent can now write a fix, build it, run it, look at the screen, and check whether it worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini, ACP, and plugins
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemini&lt;/strong&gt; is now available in the coding assistant alongside the existing options.&lt;/li&gt;
&lt;li&gt;Xcode supports the &lt;strong&gt;Agent Client Protocol (ACP)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Agents can be extended with &lt;strong&gt;plugins&lt;/strong&gt; containing skills, MCP servers, and ACP agent configurations. Skills are invokable as slash commands with completion support.&lt;/li&gt;
&lt;li&gt;Apple ships its own &lt;strong&gt;specialists&lt;/strong&gt; for targeted tasks like localization, UIKit resizing, and accessibility.&lt;/li&gt;
&lt;li&gt;Two security-focused skills landed in beta 3: &lt;code&gt;adopt-c-bounds-safety&lt;/code&gt; for a file-by-file &lt;code&gt;-fbounds-safety&lt;/code&gt; adoption workflow, and &lt;code&gt;audit-xcode-security-settings&lt;/code&gt; to suggest security-oriented build settings and entitlements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugin authors can customize how their MCP servers appear in the UI using &lt;code&gt;_meta&lt;/code&gt; fields:&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;"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;"MyGreatPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An awesome MCP server configuration."&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;"1.0.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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MyGreatMCP"&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;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tools"&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;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_meta"&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;"ideToolIconPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./icon.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ideToolIconRendersAsTemplate"&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;"ideToolTitles"&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;"whoami"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Who Am I"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"get-current-email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get Current Email Message"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A sandbox for agents
&lt;/h3&gt;

&lt;p&gt;Coding Intelligence includes a &lt;strong&gt;new security layer that monitors and controls filesystem access&lt;/strong&gt; by coding agents and any processes they spawn. It is opt-in via Coding Intelligence settings. If you are nervous about letting an agent loose in your repo, turn this on before you do anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  One known issue to watch
&lt;/h3&gt;

&lt;p&gt;If the "Implement the plan?" confirmation bar appears while the agent is still streaming, clicking Yes or No can start a new agent turn on top of the in-flight one and leave the conversation in a broken state. &lt;strong&gt;Wait for the agent to finish responding before confirming.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: Device Hub replaces the Simulator workflow
&lt;/h2&gt;

&lt;p&gt;Device Hub is the new unified interface for both simulators and physical devices. Two features stand out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wireless pairing
&lt;/h3&gt;

&lt;p&gt;You can now pair iPhone, iPad, and Apple Watch running OS 27 or later &lt;strong&gt;over a network&lt;/strong&gt;. Click the &lt;code&gt;+&lt;/code&gt; button in the Device Hub sidebar and choose "Pair Nearby Device". No cable needed for iPhone and iPad, and watch pairing is noticeably more reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mouse and trackpad gestures on iOS
&lt;/h3&gt;

&lt;p&gt;Standard Mac gestures (scrolling, pinching, rotating) now work with UIKit components on iOS devices, physical or simulated.&lt;/p&gt;

&lt;p&gt;There is a nuance worth understanding here. When you scroll with a pointing device, &lt;code&gt;UIEvent.EventType.scroll&lt;/code&gt; is emitted. Pinch or rotate produces &lt;code&gt;UIEvent.EventType.transform&lt;/code&gt;. But clicking with a mouse produces a simulated finger touch of type &lt;code&gt;UITouch.TouchType.direct&lt;/code&gt;, &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;UITouch.TouchType.indirectPointer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is a convenience hybrid, not a faithful simulation. To validate real pointer behavior, use "Simulate Trackpad or Mouse" from the Device menu, test on a physical iPad with a paired pointing device, or use iPhone Mirroring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simulator boot is faster
&lt;/h3&gt;

&lt;p&gt;Simulator runtimes now ship with a &lt;strong&gt;pre-built dyld cache&lt;/strong&gt;, which makes the first launch of a simulator much faster. Small change, noticeable every single day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Device Hub rough edges
&lt;/h3&gt;

&lt;p&gt;Beta software, so expect some friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Game controllers only work with the visionOS simulator.&lt;/li&gt;
&lt;li&gt;Video and input for a physical Apple Vision Pro are not supported (use AirPlay to view remotely). Everything else, like settings and DeviceFS, works.&lt;/li&gt;
&lt;li&gt;Two-finger touches cannot be sent.&lt;/li&gt;
&lt;li&gt;Scrolling over an Apple Watch face does not emulate the digital crown. Move the pointer over the crown in the bezel instead.&lt;/li&gt;
&lt;li&gt;Devices may not appear during parallel testing even though tests are running. Disable parallel runs if you want to watch UI tests execute.&lt;/li&gt;
&lt;li&gt;Downloaded app data containers land in a folder named after the bundle identifier rather than a proper &lt;code&gt;.xcappdata&lt;/code&gt; bundle. The workaround is to rename the folder with an &lt;code&gt;.xcappdata&lt;/code&gt; extension and nest the contents inside an &lt;code&gt;AppData&lt;/code&gt; subfolder.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Part 4: Previews and Playgrounds
&lt;/h2&gt;

&lt;p&gt;Previews got real quality-of-life work this cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Argument grids
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;#Preview(arguments:)&lt;/code&gt; renders a grid of previews, one per argument. Click any cell to open it in Interactive mode. This is great for state matrices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="cp"&gt;#Preview(arguments: [&lt;/span&gt;
    &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;singleItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;manyItems&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="kt"&gt;OrderSummaryView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resizable canvas
&lt;/h3&gt;

&lt;p&gt;iOS previews get a &lt;strong&gt;Resizable Canvas&lt;/strong&gt; mode so you can view your view in arbitrarily sized containers rather than fixed device frames. Helpful for adaptive layout work, and no longer constrained to specific size ratios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Each &lt;code&gt;#Preview&lt;/code&gt; and &lt;code&gt;#Playground&lt;/code&gt; tab can be &lt;strong&gt;pinned independently&lt;/strong&gt; in the canvas.&lt;/li&gt;
&lt;li&gt;You can preview your UI &lt;strong&gt;in a different localization&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Code inside &lt;code&gt;#Preview&lt;/code&gt; now explicitly runs on the main actor, so calling main-actor-isolated APIs no longer produces concurrency warnings or runtime check failures. That was a real annoyance under strict concurrency.&lt;/li&gt;
&lt;li&gt;Holding Command routes zoom and scroll events to the canvas. Toggleable via Editor &amp;gt; Canvas.&lt;/li&gt;
&lt;li&gt;Error messages across previews are meaningfully better: clearer timeouts, full diagnostics on MCP tool failures, and a placeholder view instead of a silent macOS fallback when a runtime is not installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Known issue:&lt;/strong&gt; standalone Swift files opened by double-clicking in Finder may fail to run &lt;code&gt;#Playground&lt;/code&gt; or &lt;code&gt;#Preview&lt;/code&gt; blocks. Use File &amp;gt; Open, or drag onto the Dock icon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: Localization becomes an agent workflow
&lt;/h2&gt;

&lt;p&gt;This is probably the most immediately practical feature for small teams.&lt;/p&gt;

&lt;p&gt;Agents can now translate strings in String Catalogs, from a single feature to an entire project, into one or more languages. Xcode handles the plumbing: it &lt;strong&gt;adds languages to your project settings, creates missing String Catalogs, and feeds context to the agent&lt;/strong&gt; as it translates.&lt;/p&gt;

&lt;p&gt;The String Catalog editor has a &lt;strong&gt;Generate Translations&lt;/strong&gt; button, and you can right-click specific strings to translate just those.&lt;/p&gt;

&lt;p&gt;Supporting changes that make this workable in a real pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A localization comment of "do not translate" automatically marks the string as &lt;strong&gt;Don't Translate&lt;/strong&gt; in String Catalogs and &lt;code&gt;translate="no"&lt;/code&gt; in exported XLIFFs.&lt;/li&gt;
&lt;li&gt;Exported XLIFFs use &lt;code&gt;state-qualifier="leveraged-mt"&lt;/code&gt; to flag machine-translated strings, so your human translators know what to review.&lt;/li&gt;
&lt;li&gt;You can annotate translations in String Catalog artifacts when agents are translating.&lt;/li&gt;
&lt;li&gt;Exporting localizations now extracts &lt;code&gt;NSLocalizedString&lt;/code&gt; and similar macros from &lt;strong&gt;header files&lt;/strong&gt;, not just implementation files. That is a fifteen-year-old bug report finally closed.&lt;/li&gt;
&lt;li&gt;The "Prepare Project for Localization" tool surfaces newly added strings as artifacts, and shows keys removed because they no longer appear in source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest take: this handles the bulk of the work, but you still want a professional translator reviewing the output before you ship to a new market.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: Instruments got a serious upgrade
&lt;/h2&gt;

&lt;p&gt;If you do performance work, this might be the most valuable section of the release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Swift Concurrency tooling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A new &lt;strong&gt;Swift Executors instrument&lt;/strong&gt; shows tracks for the Cooperative Thread Pool, the Main Actor, and any type conforming to &lt;code&gt;TaskExecutor&lt;/code&gt; or &lt;code&gt;SerialExecutor&lt;/code&gt;. Full capture on OS 27; older systems show "Unknown executor".&lt;/li&gt;
&lt;li&gt;Task tracks now group into &lt;strong&gt;Swift Task Collections&lt;/strong&gt;, sorted by name or creation site. You can switch a collection track between showing task lifetimes and task states.&lt;/li&gt;
&lt;li&gt;Tasks, Collections, Actors, and Executors have a &lt;strong&gt;Profile&lt;/strong&gt; detail showing a call tree built from data captured while the task was running. Requires recording alongside Time Profiler or CPU Profiler.&lt;/li&gt;
&lt;li&gt;Selecting a bar chart interval in an Actor or Executor queue plot lists the waiting tasks in the inspector.&lt;/li&gt;
&lt;li&gt;Tasks and Actors whose lifetime started before the trace now show up, on a best-effort basis.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;language swift task tree&lt;/code&gt; in LLDB prints a tree of every Swift Task the debugger knows about.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Foundation Models instrument
&lt;/h3&gt;

&lt;p&gt;New instrument for tracing and debugging Foundation Models usage: instructions, prompts, responses, token usage, and inference performance. If you are shipping on-device AI features, you now have visibility into them.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Trace and QoS
&lt;/h3&gt;

&lt;p&gt;System Trace unifies system calls, VM faults, and thread states into a &lt;strong&gt;single plot&lt;/strong&gt;, with a blending algorithm that keeps dense regions readable when zoomed out. You can walk the chain of scheduling events for a thread with left/right arrow keys, and the inspector offers quick actions like pinning the thread that made another thread runnable.&lt;/p&gt;

&lt;p&gt;The System Trace template also graphs &lt;strong&gt;thread priority over time&lt;/strong&gt;, which makes priority inversion and resource starvation much easier to spot. Thread Activity now displays effective QoS by default, with requested QoS available from the track dropdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything else in Instruments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Graphs no longer rescale to the local maximum when you pan the timeline, so comparisons across tracks are consistent. Manual rescale is under View &amp;gt; Rescale.&lt;/li&gt;
&lt;li&gt;Pinned tracks are restored from the previous run, and can be saved and restored explicitly via View &amp;gt; Track States.&lt;/li&gt;
&lt;li&gt;A new inspector shows details for the selected event with quick actions for pinning, filtering tracks, and filtering the detail view.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_log&lt;/code&gt; data can be overlaid on process and thread tracks via the Track Graph Display popover.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;os_signpost&lt;/code&gt; gets a track per signpost name, nested under the category.&lt;/li&gt;
&lt;li&gt;The SwiftUI instrument records more detail about layout passes and why a layout computation was not cached, plus a "Summary of Updates" focus action on the view hierarchy.&lt;/li&gt;
&lt;li&gt;Allocations marks tagged allocations with a &lt;code&gt;(tagged)&lt;/code&gt; suffix when the process runs with Memory Integrity Enforcement.&lt;/li&gt;
&lt;li&gt;Drag and drop &lt;code&gt;.atrc&lt;/code&gt;, &lt;code&gt;.logarchive&lt;/code&gt;, or &lt;code&gt;.sample&lt;/code&gt; files onto the sidebar to create a run for each.&lt;/li&gt;
&lt;li&gt;Memory usage when importing &lt;code&gt;.atrc&lt;/code&gt; files dropped by roughly 1.5 GB on average.&lt;/li&gt;
&lt;li&gt;Animation Hitches supports visionOS 27.0 and later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the command line, &lt;code&gt;xctrace record&lt;/code&gt; accepts recording options as JSON (&lt;code&gt;--show-recording-options&lt;/code&gt; prints the available ones), &lt;code&gt;xctrace export&lt;/code&gt; can restrict a time range, and export now takes &lt;code&gt;.atrc&lt;/code&gt; and &lt;code&gt;.logarchive&lt;/code&gt; directly instead of requiring an import step first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deprecation:&lt;/strong&gt; Instruments now requires iOS 17, watchOS 10, or tvOS 17 as a minimum on target devices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 7: Organizer and production insights
&lt;/h2&gt;

&lt;p&gt;The Organizer quietly became a planning tool rather than a crash dump viewer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Insights Overview&lt;/strong&gt; summarizes high-impact performance regressions across metrics and diagnostic reports, so you can prioritize instead of guessing.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Hitches&lt;/strong&gt; metric replaces Scrolling and covers all animations in your app, not just scroll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage metrics&lt;/strong&gt; track Documents &amp;amp; Data and app size across releases, which catches cache bloat and bundle growth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-driven analysis&lt;/strong&gt; generates recommendations for Crash, Energy, Disk Write, Hang, and Launch diagnostics, with links into your source and the coding assistant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metric goals&lt;/strong&gt; now cover Battery Usage, Disk Writes, Hang Rate, Hitches, Memory, and Storage. Similar-app goals are supported for Hang Rate, on-screen Battery Usage, Disk Writes, and Storage, and Launch Time baselines were recalibrated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combined with agents having access to these insights, the loop is: Organizer flags a hang regression, you ask the agent about it, the agent reads the diagnostic and proposes a fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 8: Testing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;XCUIVoiceOverService&lt;/code&gt;&lt;/strong&gt; is a new UI testing API for verifying VoiceOver behavior. You can drive VoiceOver from UI tests and assert on focus, spoken output, and navigation. Accessibility testing that actually tests accessibility.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;launch test file template&lt;/strong&gt; that opts into &lt;code&gt;runsForEachTargetApplicationUIConfiguration&lt;/code&gt;, so the test runs across every combination of orientation, localization, and appearance your app supports.&lt;/li&gt;
&lt;li&gt;Test plans let you choose how the system responds when the target app crashes during UI testing: off, warning, failure (the default), or fatal failure.&lt;/li&gt;
&lt;li&gt;Mixing frameworks now warns you: calling an XCTest assertion inside a Swift Testing test (or the reverse) produces a warning-severity runtime issue. Configurable via the new interoperability setting in your test plan.&lt;/li&gt;
&lt;li&gt;Filters were added to the test plan configurations tab, plus recent tests and open tests filters in the Test Navigator.&lt;/li&gt;
&lt;li&gt;Large Swift Testing suites with many parameterized cases perform significantly better.&lt;/li&gt;
&lt;li&gt;Test Repetition Mode now repeats individual Swift Testing cases instead of the whole test plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the SwiftPM side, &lt;code&gt;swift test&lt;/code&gt; summarizes failures at the end of a run, and supports &lt;code&gt;--maximum-repetitions&lt;/code&gt; with &lt;code&gt;--repeat-until [pass|fail]&lt;/code&gt; for hunting flaky tests. Only cases matching the condition get repeated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;swift &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--maximum-repetitions&lt;/span&gt; 20 &lt;span class="nt"&gt;--repeat-until&lt;/span&gt; fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 9: Everything else worth a mention
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Interface Builder builds without a simulator
&lt;/h3&gt;

&lt;p&gt;A new IB compilation mode, &lt;code&gt;toolchain&lt;/code&gt;, is &lt;strong&gt;enabled by default&lt;/strong&gt; for UIKit documents. It compiles IB documents without downloading a simulator, which is a genuine win for CI and build servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background Assets
&lt;/h3&gt;

&lt;p&gt;Since On Demand Resources is deprecated, this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asset-pack manifests support path wildcards, file exclusion, hard-coded source roots, and custom destination subpaths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localized asset packs&lt;/strong&gt;: the system delivers only the packs matching the user's preferred languages, cutting storage usage.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Steam Asset Converter&lt;/strong&gt; turns Steam depots into asset packs.&lt;/li&gt;
&lt;li&gt;Xcode can serve asset packs to your app while debugging on device. Set a Background Asset Packs folder in the Run scheme action's Options tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  StoreKit testing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;New configuration UI for &lt;strong&gt;In-App Purchase offer codes&lt;/strong&gt;, plus off-device purchase options to test them through the Transaction Manager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription Bundles&lt;/strong&gt; and &lt;strong&gt;Subscription Suites&lt;/strong&gt; can be configured for local testing.&lt;/li&gt;
&lt;li&gt;Volume purchase transactions can be created for 1-month and 1-year auto-renewing subscriptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Swift and C++ interoperability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;C++ constructors with default parameter expressions no longer require passing every argument explicitly from Swift.&lt;/li&gt;
&lt;li&gt;Swift closures convert to &lt;code&gt;std::function&lt;/code&gt; instances.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__counted_by&lt;/code&gt; and &lt;code&gt;__noescape&lt;/code&gt;-annotated &lt;code&gt;std::span&lt;/code&gt; parameters map to Swift &lt;code&gt;Span&lt;/code&gt; without the experimental feature flag. Return values via &lt;code&gt;__lifetimebound&lt;/code&gt; still need it.&lt;/li&gt;
&lt;li&gt;Safe wrappers can be generated for functions taking &lt;code&gt;std::span&lt;/code&gt; directly, without hiding the instantiation behind a typedef, as long as the parameter is &lt;code&gt;__noescape&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The new &lt;code&gt;SWIFT_REFCOUNTED_PTR&lt;/code&gt; macro bridges smart pointers to intrusively reference-counted types into Swift classes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;LLDB can inspect data types with &lt;code&gt;~Copyable&lt;/code&gt; fields in the standard library and system frameworks.&lt;/li&gt;
&lt;li&gt;LLDB ships with an MCP server (&lt;code&gt;lldb-mcp&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In projects using bridging headers, LLDB imports explicitly built Swift modules and PCH from DerivedData directly. This can dramatically speed up the first &lt;code&gt;po&lt;/code&gt; in a debug session, which has historically been painfully slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documentation search
&lt;/h3&gt;

&lt;p&gt;You can search developer documentation using &lt;strong&gt;natural language&lt;/strong&gt;, with results matched semantically rather than by keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  Icon Composer 2.0
&lt;/h3&gt;

&lt;p&gt;Supports a sharper rendering mode for the 2027 operating systems, with refractivity, outside specular, and deeper shadows. Edit the new properties in the group inspector and preview either design generation from the toolbar. One icon covers all OS versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration checklist
&lt;/h2&gt;

&lt;p&gt;If you want the short version, here it is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm you are on an Apple silicon Mac with macOS Tahoe 26.4+.&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;-ld_classic&lt;/code&gt; from your linker flags.&lt;/li&gt;
&lt;li&gt;Decide your Intel story: lower your deployment target, or add &lt;code&gt;x86_64&lt;/code&gt; to &lt;code&gt;ARCHS&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;li&gt;Migrate &lt;code&gt;PreviewProvider&lt;/code&gt; to &lt;code&gt;#Preview&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Plan the move from On Demand Resources to Background Assets.&lt;/li&gt;
&lt;li&gt;Check for duplicate Clang module names if dependency scanning starts failing.&lt;/li&gt;
&lt;li&gt;Fix any &lt;code&gt;init&lt;/code&gt; accessor ordering issues flagged by the Swift compiler.&lt;/li&gt;
&lt;li&gt;If you use C++, audit &lt;code&gt;multimap&lt;/code&gt;/&lt;code&gt;multiset&lt;/code&gt; &lt;code&gt;find&lt;/code&gt; usage and any custom comparators.&lt;/li&gt;
&lt;li&gt;Turn on the agent filesystem security layer before letting agents run.&lt;/li&gt;
&lt;li&gt;Drop your minimum device targets for testing to iOS 17 or later.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Reference&lt;br&gt;
Source: &lt;a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-27-release-notes" rel="noopener noreferrer"&gt;Xcode 27 Release Notes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>Reasonix - Deepseek: A Terminal Coding Agent Built Around the Thing Everyone Else Ignores</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:27:59 +0000</pubDate>
      <link>https://dev.to/arshtechpro/reasonix-deepseek-a-terminal-coding-agent-built-around-the-thing-everyone-else-ignores-3l21</link>
      <guid>https://dev.to/arshtechpro/reasonix-deepseek-a-terminal-coding-agent-built-around-the-thing-everyone-else-ignores-3l21</guid>
      <description>&lt;p&gt;Most terminal coding agents are architecturally similar: a loop, a tool registry, some context management, a TUI. &lt;a href="https://github.com/esengine/DeepSeek-Reasonix" rel="noopener noreferrer"&gt;Reasonix&lt;/a&gt; picks a different thing to optimize for, and it is a thing that shows up on your bill rather than in a demo video.&lt;/p&gt;

&lt;p&gt;The tagline is "engineered around prefix-cache stability — leave it running." That phrase is doing a lot of work, so let's unpack it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why prefix caching is the whole pitch
&lt;/h2&gt;

&lt;p&gt;DeepSeek's API, like several others, caches the prefix of your prompt. If the next request starts with the exact same token sequence as the previous one, the provider serves those tokens from cache and bills them at a small fraction of the normal input rate. Cache hits are dramatically cheaper than cache misses.&lt;/p&gt;

&lt;p&gt;Here is the catch: it is a &lt;em&gt;prefix&lt;/em&gt; cache. The match has to start at token zero and run forward. Change one character near the top of your context and every token after it is a miss.&lt;/p&gt;

&lt;p&gt;Now think about what a typical agent harness does over a long session. It re-summarizes the conversation. It injects a fresh timestamp or a re-scanned directory tree at the top. It reorders tool definitions. It rewrites the system prompt when you switch modes. Every one of those is a mutation near the front of the context, and every one of them silently invalidates the entire cache.&lt;/p&gt;

&lt;p&gt;The result is an agent that feels fine and costs several times what it should. You do not notice, because nothing errors. You just watch the number go up.&lt;/p&gt;

&lt;p&gt;Reasonix's central design constraint is: don't do that. Keep the front of the context stable, append rather than mutate, and put churn where it costs least.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that looks like in practice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A small, stable environment summary is injected at startup rather than regenerated each turn.&lt;/li&gt;
&lt;li&gt;Stale tool output gets snipped and pruned &lt;em&gt;before&lt;/em&gt; summary compaction kicks in, so a giant &lt;code&gt;cat&lt;/code&gt; result from twenty turns ago is not still sitting in your prefix.&lt;/li&gt;
&lt;li&gt;The built-in tool schema contract is documented and regression-reviewed, because a silent tool-definition reshuffle is a cache invalidation with no visible symptom.&lt;/li&gt;
&lt;li&gt;Two-model mode (executor plus planner) runs each model in its own separate, cache-stable session instead of interleaving them into one context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the neatest idea in the project. The naive way to add a planner is to inject planning turns into the same conversation, which trashes cache stability for both roles. Keeping them in separate sessions means each one's prefix stays intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A single static Go binary.&lt;/strong&gt; &lt;code&gt;CGO_ENABLED=0&lt;/code&gt;, cross-compiles to six targets, and the only dependency is a TOML parser. No Node runtime, no Python venv, no dependency tree to audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config-driven, not model-hardcoded.&lt;/strong&gt; Providers, agent settings, enabled tools, and plugins all live in a &lt;code&gt;reasonix.toml&lt;/code&gt;. DeepSeek ships as a preset, but any OpenAI-compatible endpoint is a config entry rather than a code change. Secrets come from the environment and are never written into the config file. Despite the repo name, this is not DeepSeek-only, and it is not an official DeepSeek project — it is a community project that treats DeepSeek as the first-class default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP client.&lt;/strong&gt; External tools run as subprocesses over stdio JSON-RPC, or over Streamable HTTP for remote servers. If you already have an &lt;code&gt;.mcp.json&lt;/code&gt;, drop it in the project root and it is read as-is. Server prompts show up as slash commands, and resources are pulled into a message with &lt;code&gt;@server:uri&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissions and sandbox are separate mechanisms.&lt;/strong&gt; Permissions are policy: each tool call is evaluated deny, then ask, then allow, then fallback, and approvals are stored as reusable rules like &lt;code&gt;Bash(go test:*)&lt;/code&gt; rather than one-off clicks. The sandbox is enforcement: file writers refuse any path outside the workspace root, resolving symlinks and &lt;code&gt;..&lt;/code&gt; so a link cannot tunnel out.&lt;/p&gt;

&lt;p&gt;One caveat worth knowing before you use the autonomous mode: &lt;code&gt;bash&lt;/code&gt; itself is jailed via Seatbelt on macOS, but on other platforms it currently runs unconfined. On Linux or Windows, your deny list is the real boundary, not the sandbox. Write it accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; reasonix    &lt;span class="c"&gt;# or: brew install esengine/reasonix/reasonix&lt;/span&gt;

reasonix setup       &lt;span class="c"&gt;# pick a provider, set your key&lt;/span&gt;
reasonix             &lt;span class="c"&gt;# start an interactive session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is genuinely the whole first run. There is also a desktop app and a VS Code extension that drive the same local engine, plus prebuilt archives on every release if you would rather not go through npm. Everything else — the TOML schema, permission rules, MCP plugins, custom slash commands, two-model setup — is in the &lt;a href="https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md" rel="noopener noreferrer"&gt;Guide&lt;/a&gt;, and none of it is required to try the thing.&lt;/p&gt;

&lt;p&gt;Two commands worth knowing on day one: &lt;code&gt;/init&lt;/code&gt; generates project instructions, and &lt;code&gt;/branch &amp;lt;turn&amp;gt;&lt;/code&gt; forks the conversation from an earlier checkpointed turn. That second one is a much better recovery move than clearing context and re-explaining everything when the agent goes down a bad path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a look?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if:&lt;/strong&gt; you already pay for DeepSeek or another OpenAI-compatible endpoint and run long agent sessions, you are cost-sensitive enough to care about cache hit rates, or you want a single static binary instead of a Node or Python install. The MCP support, config-first design, and permission rules are all solid, and the project has real traction — tens of thousands of stars, an active contributor list, CI, code-signed Windows builds, and a bilingual Discord.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably not, if:&lt;/strong&gt; you are locked into a provider without prefix caching, in which case the core optimization is irrelevant and you should pick a harness on other merits. Also skip it if you need a mature autonomous-mode sandbox on Linux or Windows today — that is still on the roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to go in knowing.&lt;/strong&gt; The active branch is &lt;code&gt;main-v2&lt;/code&gt;, not &lt;code&gt;main&lt;/code&gt;, which is mildly confusing when you clone. If you are on a 0.x config, there is a migration guide, and your old MCP servers are still read as a lowest-priority source. The issue and PR counts are large, which is what you would expect from a fast-moving project at this size — read it as activity, not instability, but check the changelog before upgrading anything you depend on. And the repo name genuinely does mislead people: this is a community project, not something DeepSeek ships.&lt;/p&gt;

&lt;p&gt;The reason I would look at it even if you never install it is the design argument. Most agent harnesses treat context management as a correctness problem: keep the model informed, drop the irrelevant. Reasonix treats it as an economics problem too, where the ordering and stability of your context is a cost variable you control. That reframing is worth twenty minutes of reading whatever tool you end up using.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/esengine/DeepSeek-Reasonix" rel="noopener noreferrer"&gt;https://github.com/esengine/DeepSeek-Reasonix&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>cli</category>
      <category>deepseek</category>
    </item>
    <item>
      <title>LoopX: A Control Plane for AI Agents That Have to Keep Working for Days</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Wed, 05 Aug 2026 18:23:46 +0000</pubDate>
      <link>https://dev.to/arshtechpro/loopx-a-control-plane-for-ai-agents-that-have-to-keep-working-for-days-47n</link>
      <guid>https://dev.to/arshtechpro/loopx-a-control-plane-for-ai-agents-that-have-to-keep-working-for-days-47n</guid>
      <description>&lt;p&gt;If you have ever pointed a coding agent at a multi-day goal, you know the failure mode. It is not that the model writes a bad function. It is that on turn 40, the agent no longer remembers what the objective was, which decision you already made, what is out of scope, or what the last run actually proved. The context window rolled over, and the plot went with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/huangruiteng/loopx" rel="noopener noreferrer"&gt;LoopX&lt;/a&gt; is an attempt to fix that specific problem. It calls itself "loop engineering for long-running AI agents," and it is a local control plane that sits &lt;em&gt;above&lt;/em&gt; your agent runtime rather than replacing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-sentence version
&lt;/h2&gt;

&lt;p&gt;Your agent (Codex, Claude Code, Cursor, whatever) executes bounded loops. Something (a heartbeat, a cron job, you hitting enter) triggers the next loop. LoopX holds the state that has to survive between those loops.&lt;/p&gt;

&lt;p&gt;The project draws the separation like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codex / Claude Code / Cursor&lt;/td&gt;
&lt;td&gt;Execute a bounded agent loop: read, write, run commands, respond&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goal mode / automation / CLI / TUI&lt;/td&gt;
&lt;td&gt;Trigger or schedule the next loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoopX&lt;/td&gt;
&lt;td&gt;Preserve goals, gates, todos, run history, quota, evidence, handoff state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That third row is the whole product. LoopX is not an executor and not an autonomous production controller. It is a state kernel with a CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just use a todo file" isn't enough
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;TODO.md&lt;/code&gt; plus a long system prompt gets you surprisingly far. It falls over once any of these become true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The goal changed halfway through, and nothing recorded &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A decision genuinely needs a human, and that request evaporated into a chat message nobody read.&lt;/li&gt;
&lt;li&gt;Two agents are touching the same repo and neither knows who owns what.&lt;/li&gt;
&lt;li&gt;The last run claimed success, and there is no artifact proving it.&lt;/li&gt;
&lt;li&gt;Some work is safe and read-only, some crosses into writes, production, or private data, and the distinction lives only in your head.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LoopX makes those things explicit and machine-readable, which is what lets a loop run longer without becoming less accountable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concepts, in plain English
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lifetime goals.&lt;/strong&gt; A durable project intention that outlives one chat thread. Importantly, a lifetime goal does not hand the agent open-ended autonomy: only the next bounded transition is executable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User gates.&lt;/strong&gt; A concrete decision that belongs to you, recorded as a first-class object instead of a sentence in a transcript. The loop can see that it is blocked on a human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safe fallback.&lt;/strong&gt; When one lane is gated, audited side paths can keep moving without bypassing the gate. This is the part I find most interesting: the alternative designs are usually "block everything" or "let the agent decide," and both are bad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Todo ownership.&lt;/strong&gt; Todos are tagged user or agent, with a &lt;code&gt;claimed_by&lt;/code&gt; field so multiple agents can coordinate instead of colliding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quota.&lt;/strong&gt; A guard that answers whether an automatic turn should run right now, wait, ask the user, self-repair, or stay quiet. Practically, this is your defense against a heartbeat loop burning tokens on turns that cannot produce a verified transition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run history and evidence.&lt;/strong&gt; Compact append-only events for progress, validation, blockers, rewards, and quota spend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public/private boundary checks.&lt;/strong&gt; A local scan that tries to keep credentials, raw logs, local paths, and private state out of anything you publish.&lt;/p&gt;

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

&lt;p&gt;Requirements are refreshingly light: Python 3.11+, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;, and a macOS or Linux shell. The Python package has no runtime dependencies outside the standard library. Git is only needed if you want to contribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install (no clone)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/huangruiteng/loopx/main/scripts/install-from-github.sh | bash
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&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;/.local/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
loopx doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer drops a release snapshot under &lt;code&gt;~/.local/share/loopx/releases/&lt;/code&gt;, a CLI wrapper in &lt;code&gt;~/.local/bin&lt;/code&gt;, a man page, and reusable agent skills under &lt;code&gt;~/.codex/skills&lt;/code&gt;. As always, read a piped install script before running it if that matters to you.&lt;/p&gt;

&lt;p&gt;Updates go through an explicit interface rather than re-running the installer blind:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx update &lt;span class="nt"&gt;--check&lt;/span&gt;     &lt;span class="c"&gt;# read-only&lt;/span&gt;
loopx update &lt;span class="nt"&gt;--dry-run&lt;/span&gt;   &lt;span class="c"&gt;# read-only&lt;/span&gt;
loopx update &lt;span class="nt"&gt;--execute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Kick the tires without touching a real repo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx demo
&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/loopx-demo
loopx status
loopx quota should-run &lt;span class="nt"&gt;--goal-id&lt;/span&gt; demo-goal
loopx &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--goal-id&lt;/span&gt; demo-goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a disposable goal with one user todo and one agent todo. You should see &lt;code&gt;ok: True&lt;/code&gt; and a &lt;code&gt;should_run=True&lt;/code&gt; / &lt;code&gt;state=eligible&lt;/code&gt; quota response. Do this first. It takes thirty seconds and tells you whether the mental model clicks for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Connect a real project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your-project
loopx bootstrap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--objective&lt;/span&gt; &lt;span class="s2"&gt;"Improve this project through bounded, verified goal segments."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--goal-doc&lt;/span&gt; GOAL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;loopx connect&lt;/code&gt; is an alias for &lt;code&gt;bootstrap&lt;/code&gt;. This creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-project/
  .loopx/registry.json
  .codex/goals/your-project-goal/ACTIVE_GOAL_STATE.md

~/.codex/loopx/
  goals/&amp;lt;goal-id&amp;gt;/runs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add these to &lt;code&gt;.gitignore&lt;/code&gt; before you commit anything:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.loopx/
.codex/goals/
.opencode/goals/
goals/**/ACTIVE_GOAL_STATE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That state is live local runtime data. Committing a controller's active goal state is how private paths and internal notes end up in a public repo.&lt;/p&gt;

&lt;p&gt;A healthy connection means &lt;code&gt;loopx doctor&lt;/code&gt; passes, both files above exist, &lt;code&gt;loopx status&lt;/code&gt; shows who acts next, and none of it is staged for commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The agent-first path
&lt;/h3&gt;

&lt;p&gt;The docs actually push you toward &lt;em&gt;not&lt;/em&gt; running these commands yourself. Paste something like this into Codex, Claude Code, or Cursor from your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connect the current project to LoopX. Do not clone the LoopX repository.
If `loopx` is not on PATH, install it with the official no-clone installer.

Then run `loopx doctor`. Working only from the current project root:
1. If LoopX state already exists, reuse it. Do not overwrite the goal or objective.
2. If the project is not connected, prefer `loopx connect`; use `loopx bootstrap`
   only when state clearly needs initialization.
3. Ensure `.loopx/`, `.codex/goals/`, and `.local/` are ignored.
4. Set up the thin LoopX heartbeat for this surface.
5. Stop after setup and report the active state id, current user gate, top agent
   todo, and next safe action.

Do not start longer delivery work in this setup turn.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat worth knowing before you try this with a non-Codex agent: LoopX can only drive an agent that exposes at least one control hook, such as shell execution, a goal/task command, an automation hook, or its own scheduler. Without one, LoopX still tracks state, but you run the commands by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The daily loop
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx status
loopx &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
loopx quota should-run &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx todo add &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal &lt;span class="nt"&gt;--role&lt;/span&gt; agent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--text&lt;/span&gt; &lt;span class="s2"&gt;"Run the next bounded validation slice."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diagnosing is also meant to be delegated. &lt;code&gt;loopx diagnose&lt;/code&gt; deliberately emits an agent-facing evidence packet rather than a verdict, so you ask your agent to run it and reason from it: can this project self-drive, what blocks it, what exact question needs your answer, what happens next.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Heartbeats and quota (the token-spend part)
&lt;/h3&gt;

&lt;p&gt;An automatic turn is supposed to check quota before working and record spend exactly once after validated writeback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx quota should-run &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
loopx heartbeat-prompt &lt;span class="nt"&gt;--thin&lt;/span&gt; &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal
loopx quota spend-slot &lt;span class="nt"&gt;--goal-id&lt;/span&gt; your-project-goal &lt;span class="nt"&gt;--slots&lt;/span&gt; 1 &lt;span class="nt"&gt;--source&lt;/span&gt; heartbeat &lt;span class="nt"&gt;--execute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spend is not appended for quiet skips, preflight failures, or dry runs. &lt;code&gt;should-run&lt;/code&gt; returns a fairly rich contract: whether delivery may run, what it is waiting on (user, controller, external evidence, health, quota), the work lane, todo summaries, and the spend policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Before you publish anything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loopx check &lt;span class="nt"&gt;--scan-path&lt;/span&gt; README.md &lt;span class="nt"&gt;--scan-path&lt;/span&gt; docs/ &lt;span class="nt"&gt;--scan-path&lt;/span&gt; examples/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Optional local dashboard
&lt;/h3&gt;

&lt;p&gt;There is a read-first React dashboard for inspecting projects, todos, gates, and evidence across a global registry. It is explicitly experimental: the CLI stays the source of truth and browser writes require local opt-in. Check the repo docs for the current path, since it moved between the README and the getting-started guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately does not do
&lt;/h2&gt;

&lt;p&gt;The maintainer is unusually direct about this, which is a good sign. LoopX is not a full agent platform, not an autonomous production controller, and not a replacement for your runtime. Project ownership and dangerous permissions stay with the human. It is a local coordination substrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it worth a look?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes, if:&lt;/strong&gt; you are running agents on goals that span days, you have heartbeat or monitor-style turns firing on a schedule, you coordinate a controller agent with scoped side agents, or you have already been bitten by an agent confidently redoing work it finished last Tuesday. The demo costs you a minute, and the ideas are portable even if you never adopt the tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably not yet, if:&lt;/strong&gt; you use agents for single-session tasks, you are on Windows without WSL, you need something battle-tested with a release history and a large user base, or you do not use Codex-family or shell-capable agents. The value shows up proportionally to how long your loops run. Short loops do not drift, and the ceremony will feel like overhead.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/huangruiteng/loopx" rel="noopener noreferrer"&gt;https://github.com/huangruiteng/loopx&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>Qwen3.8-Max Just Went GA: A Developer's Guide to Alibaba's 2.4T Model</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 03 Aug 2026 22:40:19 +0000</pubDate>
      <link>https://dev.to/arshtechpro/qwen38-max-just-went-ga-a-developers-guide-to-alibabas-24t-model-ff3</link>
      <guid>https://dev.to/arshtechpro/qwen38-max-just-went-ga-a-developers-guide-to-alibabas-24t-model-ff3</guid>
      <description>&lt;p&gt;Alibaba made Qwen3.8-Max generally available on &lt;strong&gt;August 3, 2026&lt;/strong&gt;. &lt;br&gt;
This is a practical rundown for developers: what the model is, what it costs, how to call it, and where the claims still need a pinch of salt.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one-line version
&lt;/h2&gt;

&lt;p&gt;Qwen3.8-Max is a 2.4-trillion-parameter Mixture-of-Experts model with a 1M-token context window, native text/image/video input, OpenAI-compatible API, and pricing of $2 in / $6 out per million tokens. Open weights are promised for next week.&lt;/p&gt;
&lt;h2&gt;
  
  
  First, the naming confusion
&lt;/h2&gt;

&lt;p&gt;The version number trips people up, so let's clear it up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3&lt;/strong&gt; is the open-weight model family you've probably used (Qwen3-32B, Qwen3-235B-A22B, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen3.5 / 3.6 / 3.7 / 3.8&lt;/strong&gt; are &lt;em&gt;successive flagship generations&lt;/em&gt;, not point releases of Qwen3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Max"&lt;/strong&gt; is the top tier of each generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Qwen3.8-Max is not "Qwen 3, version 8." It is the newest flagship, and it succeeds Qwen3.7-Max from May 2026. &lt;cite&gt;Qwen describes it as their most capable model to date, and the first open-weight model at Max scale.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Also note there is a &lt;strong&gt;Qwen3.8-Max-Preview&lt;/strong&gt; (July 19) and now &lt;strong&gt;Qwen3.8-Max&lt;/strong&gt; (August 3, GA). If you're reading a blog post from July, it's about the preview, and half its "not disclosed yet" list has since been answered.&lt;/p&gt;
&lt;h2&gt;
  
  
  The specs that matter
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Spec&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total parameters&lt;/td&gt;
&lt;td&gt;2.4 trillion (sparse MoE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active parameters per token&lt;/td&gt;
&lt;td&gt;~95 billion (reported)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context window&lt;/td&gt;
&lt;td&gt;1,000,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max input&lt;/td&gt;
&lt;td&gt;991K tokens (983K with thinking on)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max output&lt;/td&gt;
&lt;td&gt;131K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max reasoning budget&lt;/td&gt;
&lt;td&gt;262K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input modalities&lt;/td&gt;
&lt;td&gt;Text, image, video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limits&lt;/td&gt;
&lt;td&gt;2M tokens/min, 15K requests/min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3.8-max&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Why "95B active" is the number to care about
&lt;/h3&gt;

&lt;p&gt;This is the part worth understanding properly, because "2.4 trillion parameters" is close to meaningless on its own.&lt;/p&gt;

&lt;p&gt;A sparse Mixture-of-Experts model is not one giant network. Each layer holds many specialist sub-networks ("experts"), and a router picks a small handful for each token. The rest sit idle for that token.&lt;/p&gt;

&lt;p&gt;Qwen's own smaller models make the pattern obvious from the naming: &lt;cite&gt;Qwen3-235B-A22B carries 235B total parameters but activates 22B per token, and Qwen3-30B-A3B activates roughly 3B.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total parameters&lt;/strong&gt; ≈ how much the model &lt;em&gt;knows&lt;/em&gt; (and how much memory you'd need to host it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active parameters&lt;/strong&gt; ≈ how much compute each token actually costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 2.4T total / ~95B active, roughly 4% of the network fires per token. That's why Alibaba can sell it at $2/M input rather than something ruinous.&lt;/p&gt;

&lt;p&gt;One honesty note: this figure needs a small asterisk. &lt;cite&gt;MarkTechPost's launch coverage stated Alibaba had not disclosed the activated-parameter count&lt;/cite&gt;, while &lt;cite&gt;benchmark trackers report that Qwen's own August 3 release post lists 2.4T total with 95B active&lt;/cite&gt;. &lt;cite&gt;Other coverage advises treating the number as reported rather than confirmed until Alibaba publishes a model card.&lt;/cite&gt; Use it for rough cost intuition, not for capacity planning.&lt;/p&gt;

&lt;p&gt;And to be clear: 95B active does &lt;strong&gt;not&lt;/strong&gt; mean you can serve this on a 95B-sized box. &lt;cite&gt;A serving system still needs fast access to the full expert pool, plus attention state, routing machinery, multimodal components and runtime buffers.&lt;/cite&gt; The whole 2.4T checkpoint has to be resident somewhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pricing, and the one trick that dominates your bill
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price per 1M tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$6.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input (implicit cache read)&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit cache creation&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit cache read&lt;/td&gt;
&lt;td&gt;$0.17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the thing to internalize: &lt;cite&gt;cached input is eight times cheaper than fresh input, which means prefix stability drives your cost more than prompt length does.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;That single sentence should change how you architect against this model. Concretely, imagine an agent loop with a 200K-token stable prefix (system prompt, tool schemas, codebase context) running 50 turns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No caching:      50 × 0.2M × $2.00                = $20.00
Explicit cache:  (0.2M × $2.50) + (50 × 0.2M × $0.17) = $2.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly a 9x difference, from nothing but keeping your prefix byte-stable. Practical implications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put everything static at the &lt;strong&gt;front&lt;/strong&gt; of your prompt, and everything variable at the end&lt;/li&gt;
&lt;li&gt;Don't inject timestamps, request IDs or shuffled context into your system prompt&lt;/li&gt;
&lt;li&gt;If you're rebuilding the prefix per request, you're paying 8x for no reason&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare against the field: &lt;cite&gt;Kimi K3 runs $3.00 input / $15.00 output per million tokens&lt;/cite&gt;, so Qwen3.8-Max undercuts it meaningfully, particularly on output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the API
&lt;/h2&gt;

&lt;p&gt;Integration is deliberately boring, which is the point. &lt;cite&gt;The hosted API is OpenAI- and DashScope-compatible, so integration is a base-URL and model-ID change.&lt;/cite&gt; &lt;cite&gt;It also supports the Anthropic protocol, so tools such as Cursor, Cline, Codex and Claude Code can point at it through existing integrations.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Here's the multimodal example straight from the model page, using the DashScope SDK:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;dashscope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_http_api_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://dashscope-intl.aliyuncs.com/api/v1&lt;/span&gt;&lt;span class="sh"&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="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;image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/your-image.jpeg&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;text&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;What is depicted in the image?&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dashscope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MultiModalConversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&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="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DASHSCOPE_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;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;qwen3.8-max&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're already on the OpenAI SDK, you point &lt;code&gt;base_url&lt;/code&gt; at Alibaba's compatible-mode endpoint and change the model string to &lt;code&gt;qwen3.8-max&lt;/code&gt;. Grab the exact base URL from &lt;code&gt;docs.qwencloud.com&lt;/code&gt; rather than guessing, since it differs between the international and China-domestic deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch out for the two regional endpoints.&lt;/strong&gt; &lt;cite&gt;Availability is split between an international deployment (qwencloud.com) and a China-domestic one (platform.qianwenai.com), each requiring separate registration and billing.&lt;/cite&gt; Keys are not interchangeable.&lt;/p&gt;

&lt;p&gt;It's also on third-party gateways already. &lt;cite&gt;Vercel's AI Gateway added it as &lt;code&gt;alibaba/qwen3.8-max&lt;/code&gt; on August 2 at provider pricing with no markup.&lt;/cite&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported features
&lt;/h3&gt;

&lt;p&gt;&lt;cite&gt;Function calling, structured outputs, batches, prefix completion and fine-tuning are all supported. Five built-in tools ship on the Responses API: &lt;code&gt;code_interpreter&lt;/code&gt;, &lt;code&gt;web_search&lt;/code&gt;, &lt;code&gt;web_extractor&lt;/code&gt;, &lt;code&gt;t2i_search&lt;/code&gt; and &lt;code&gt;i2i_search&lt;/code&gt;.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;The built-in tools are worth a look before you hand-roll your own. If you're currently maintaining a custom web-search tool wrapper, that's now a server-side flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks: the honest read
&lt;/h2&gt;

&lt;p&gt;Unlike the July preview, GA came with actual numbers. &lt;cite&gt;Alibaba published a benchmark table with the formal launch showing 86.6 on Terminal-Bench 2.1, 67.7 on SWE-bench Pro, and 92.6 on GPQA Diamond, with the strongest gains in multimodal and agentic categories rather than general reasoning.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Filling in the competitive picture:&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;Qwen3.8-Max&lt;/th&gt;
&lt;th&gt;Claude Fable 5&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;Terminal-Bench 2.1&lt;/td&gt;
&lt;td&gt;86.6&lt;/td&gt;
&lt;td&gt;84.6&lt;/td&gt;
&lt;td&gt;GPT-5.6 Sol (max) leads at 88.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWE-bench Pro&lt;/td&gt;
&lt;td&gt;67.7&lt;/td&gt;
&lt;td&gt;80.0&lt;/td&gt;
&lt;td&gt;Fable 5 ahead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FrontierSWE&lt;/td&gt;
&lt;td&gt;73.5&lt;/td&gt;
&lt;td&gt;88.8&lt;/td&gt;
&lt;td&gt;Fable 5 ahead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPQA Diamond&lt;/td&gt;
&lt;td&gt;92.6&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Up marginally from 3.7-Max's 92.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PaperBench&lt;/td&gt;
&lt;td&gt;93.0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Qwen leads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IFBench&lt;/td&gt;
&lt;td&gt;82.8&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Qwen leads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;cite&gt;It also tops most vision rows, including OSWorld-Verified at 86.1, Parametric CAD Bench at 91.5, and OmniDocBench 1.5 at 92.1. Against its own predecessor the jump is large: DeepSWE 1.1 moves from 21.6 to 56.6, FrontierSWE from 40.7 to 73.5, and JobBench from 31.3 to 53.4.&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Two caveats that belong in any fair reading, both flagged by MarkTechPost: &lt;cite&gt;the multimodal table benchmarks against Qwen3.7-Plus rather than Qwen3.7-Max, which flatters the generational delta; and Alibaba's own RL scaling curve peaks at 0.725 near 4,000 training environments, then declines to 0.719 and 0.689.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Open weights: read the fine print
&lt;/h2&gt;

&lt;p&gt;&lt;cite&gt;Alibaba confirmed open weights ship next week, along with a second checkpoint, Qwen3.8-27B.&lt;/cite&gt; &lt;cite&gt;Releases are expected the week of August 10 via Alibaba Cloud Model Studio.&lt;/cite&gt; &lt;cite&gt;This marks Alibaba's return to open-sourcing its top-tier models after keeping several recent flagships proprietary earlier this year.&lt;/cite&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's still missing
&lt;/h2&gt;

&lt;p&gt;Being straight about the gaps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No model card.&lt;/strong&gt; &lt;cite&gt;The GA launch still did not provide an official training and safety model card.&lt;/cite&gt; No training data disclosure, no safety evaluation methodology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No published license.&lt;/strong&gt; Until the weights land, there's nothing to review on commercial-use terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active parameter count is reported, not officially confirmed&lt;/strong&gt; in a spec sheet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No independent Artificial Analysis score&lt;/strong&gt; yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Should you use it?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Try it now if:&lt;/strong&gt; you're doing multimodal work (documents, video indexing, screenshots, UI automation), you want long-context agent runs at a fraction of Western frontier pricing, or you have an existing OpenAI/Anthropic-protocol setup where testing costs you a base-URL change and an afternoon.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.qwencloud.com/models/qwen3.8-max" rel="noopener noreferrer"&gt;Qwen3.8-Max model page (QwenCloud)&lt;/a&gt; — canonical specs, pricing, rate limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Running this in production yet? I'd be interested in real latency and tokens-per-second numbers, since Alibaba hasn't published throughput figures.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agentskills</category>
      <category>webdev</category>
    </item>
    <item>
      <title>AirLLM Runs a 70B Model on a 4GB GPU. It's True, and That's Not the Interesting Part</title>
      <dc:creator>ArshTechPro</dc:creator>
      <pubDate>Mon, 03 Aug 2026 22:23:56 +0000</pubDate>
      <link>https://dev.to/arshtechpro/airllm-runs-a-70b-model-on-a-4gb-gpu-its-true-and-thats-not-the-interesting-part-hha</link>
      <guid>https://dev.to/arshtechpro/airllm-runs-a-70b-model-on-a-4gb-gpu-its-true-and-thats-not-the-interesting-part-hha</guid>
      <description>&lt;p&gt;AirLLM's README opens with a line that sounds like it can't be true:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AirLLM dramatically reduces inference memory usage, letting 70B large language models run on a single 4GB GPU card — without quantization, distillation, or pruning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So let's actually check it. Is the claim real? How do you set it up? And — the question nobody asks loudly enough — &lt;strong&gt;should you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The claim is technically true and the engineering is legitimate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The trick, in one paragraph
&lt;/h2&gt;

&lt;p&gt;Here's the thing about a transformer: it's a stack of layers, and it runs them &lt;strong&gt;in order&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input → Layer 1 → Layer 2 → Layer 3 → ... → Layer 80 → Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Layer 1 is computing, layers 2 through 80 are just... sitting in your VRAM. Doing nothing. Taking up space.&lt;/p&gt;

&lt;p&gt;Normal inference loads all 80 layers into GPU memory because keeping them there is fast. AirLLM asks the obvious follow-up question: what if we didn't? Load Layer 1, run it, throw it away, load Layer 2, run it, throw it away.&lt;/p&gt;

&lt;p&gt;Now your VRAM requirement isn't "the size of the model." It's &lt;strong&gt;"the size of the single largest layer."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a 70B model at full FP16 precision, that's roughly 1.75GB per layer. Which fits in 4GB with room to spare. The model is still 140GB — it just lives on your disk instead of your GPU, streaming through one slice at a time.&lt;/p&gt;

&lt;p&gt;That's it. That's the whole idea. And it genuinely works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fact-check: is the claim real?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes — with an asterisk the size of the model itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's take the claims one at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  "70B on a single 4GB GPU"
&lt;/h3&gt;

&lt;p&gt;Real. The math checks out (~1.75GB per layer at FP16), and it's been independently reproduced by enough people that this isn't in dispute.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Without quantization, distillation, or pruning"
&lt;/h3&gt;

&lt;p&gt;Real, and this is the genuinely interesting part. Most "run big models on small hardware" tricks work by &lt;em&gt;making the model worse&lt;/em&gt; — squashing weights from 16 bits to 4, which costs you some accuracy. AirLLM doesn't have to. You get the actual, unmodified, full-precision model.&lt;/p&gt;

&lt;p&gt;(Quantization is &lt;em&gt;optional&lt;/em&gt; here — you can pass &lt;code&gt;compression='4bit'&lt;/code&gt; to make it faster. But you don't have to, and that's the distinction they're drawing.)&lt;/p&gt;

&lt;h3&gt;
  
  
  The bigger numbers, too
&lt;/h3&gt;

&lt;p&gt;The README's scaling table looks absurd but follows from the same logic:&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;Size&lt;/th&gt;
&lt;th&gt;Claimed VRAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.x 70B&lt;/td&gt;
&lt;td&gt;70B&lt;/td&gt;
&lt;td&gt;~4 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 3.1 405B&lt;/td&gt;
&lt;td&gt;405B&lt;/td&gt;
&lt;td&gt;~8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-V3&lt;/td&gt;
&lt;td&gt;671B&lt;/td&gt;
&lt;td&gt;~12 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-235B (MoE)&lt;/td&gt;
&lt;td&gt;235B&lt;/td&gt;
&lt;td&gt;~3 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kimi K3 (MoE)&lt;/td&gt;
&lt;td&gt;2.8T&lt;/td&gt;
&lt;td&gt;~3.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice something weird? &lt;strong&gt;The 2.8-trillion-parameter model needs less VRAM than the 671B one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's not an error. Those are Mixture-of-Experts models. An MoE layer contains hundreds of "expert" sub-networks, but each token only routes to a handful of them. Per the v3.1.0 release notes, Kimi K3 holds 896 experts per layer and routes each token to just 16 — so while a full layer's experts expand to ~55GB, a single token only actually needs ~1GB of them. AirLLM streams &lt;em&gt;just those experts&lt;/em&gt; instead of the whole layer.&lt;/p&gt;

&lt;p&gt;Sparser model → smaller working set → less VRAM. Counterintuitive, correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the headline leaves out
&lt;/h3&gt;

&lt;p&gt;Here's the part that isn't in the big bold text. From AirLLM's &lt;strong&gt;own v3.1.0 release notes&lt;/strong&gt;, measured on an RTX 6000 Ada:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peak VRAM during generation&lt;/td&gt;
&lt;td&gt;3.72 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-time init&lt;/td&gt;
&lt;td&gt;900 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation speed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;292 s/token, disk-bound&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To be clear about what that means: a 100-token response would take &lt;strong&gt;just over 8 hours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Credit where it's due — the maintainer publishes this honestly in the release notes. It's just not the number on the marketing line.&lt;/p&gt;

&lt;p&gt;For more typical setups, community reports land in the range of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;70B on a decent NVMe:&lt;/strong&gt; roughly 5–35 seconds per token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;70B on a MacBook:&lt;/strong&gt; reports as low as ~0.07 tokens/sec (~14 s/token)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For comparison, llama.cpp with a quantized 70B on an RTX 4090:&lt;/strong&gt; 8–15 tokens &lt;em&gt;per second&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the honest version of the claim is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AirLLM doesn't make 70B fast on a 4GB GPU. It makes 70B &lt;em&gt;possible&lt;/em&gt; on a 4GB GPU.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why it's slow (do this math once and you'll never be confused again)
&lt;/h2&gt;

&lt;p&gt;This is worth internalizing, because it explains everything and it's not complicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To generate one token, the model must run every single layer.&lt;/strong&gt; Which means AirLLM must read &lt;strong&gt;the entire model off disk&lt;/strong&gt; — for &lt;strong&gt;every token&lt;/strong&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seconds per token ≈ model size on disk ÷ disk read speed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's plug in a 70B model at FP16 (~140GB):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Time per token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gen4 NVMe SSD&lt;/td&gt;
&lt;td&gt;~7 GB/s&lt;/td&gt;
&lt;td&gt;~20 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gen3 NVMe SSD&lt;/td&gt;
&lt;td&gt;~3.5 GB/s&lt;/td&gt;
&lt;td&gt;~40 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SATA SSD&lt;/td&gt;
&lt;td&gt;~0.5 GB/s&lt;/td&gt;
&lt;td&gt;~280 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spinning HDD&lt;/td&gt;
&lt;td&gt;~0.15 GB/s&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Your disk is your inference engine.&lt;/strong&gt; The GPU is barely working — it's sitting idle waiting for data. This is why AirLLM users report their fans screaming and their laptop becoming unusable: the bottleneck is I/O and CPU, not compute.&lt;/p&gt;

&lt;p&gt;Two consequences that fall right out of this formula:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;compression='4bit'&lt;/code&gt;.&lt;/strong&gt; It shrinks the bytes you have to read by ~4x. The README advertises up to 3x speedup, and now you know exactly why — this isn't about faster math, it's about moving less data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM is secretly your best upgrade.&lt;/strong&gt; If your system RAM can hold a big chunk of the model, the OS page cache serves layers from memory instead of disk. This is why people with 128GB machines report dramatically better numbers than the raw disk math predicts.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;h3&gt;
  
  
  Requirements — read this part before you start
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Disk space is the #1 thing that will bite you.&lt;/strong&gt; AirLLM downloads the model, &lt;em&gt;then&lt;/em&gt; decomposes it into per-layer shards. For a while, you have &lt;strong&gt;both copies on disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a 70B FP16 model, budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~140GB (original download)
+ ~140GB (layer shards)
= ~280GB free space
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single most common error in the repo's FAQ — &lt;code&gt;safetensors_rust.SafetensorError: Error while deserializing header: MetadataIncompleteBuffer&lt;/code&gt; — is, per the maintainers, almost always just &lt;strong&gt;you ran out of disk&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll also want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An NVMe SSD (not SATA, definitely not HDD)&lt;/li&gt;
&lt;li&gt;As much system RAM as you can get&lt;/li&gt;
&lt;li&gt;A Hugging Face token for gated models like Llama&lt;/li&gt;
&lt;li&gt;Patience. Real, genuine patience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Install
&lt;/h3&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;airllm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the 4-bit compression speedup (recommended — see the math above):&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;-U&lt;/span&gt; bitsandbytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Run it
&lt;/h3&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;airllm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen3-32B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4bit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# ~3x faster; skip for full precision
&lt;/span&gt;    &lt;span class="n"&gt;delete_original&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="c1"&gt;# deletes the original after splitting — saves ~50% disk
&lt;/span&gt;    &lt;span class="n"&gt;profiling_mode&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="c1"&gt;# logs per-layer timing so you can see the bottleneck
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;input_text&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;What is the capital of United States?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;input_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_attention_mask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;truncation&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;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# avoids a common tokenizer error
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;generation_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;cuda&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_cache&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;return_dict_in_generate&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="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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generation_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sequences&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole API. Swapping to a 671B model is a one-line change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek-ai/DeepSeek-V3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 671B, ~12GB VRAM
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start small.&lt;/strong&gt; Please run an 8B model first to validate your setup before committing 280GB and several hours to a 70B download.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Useful config flags
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&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;&lt;code&gt;compression&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;'4bit'&lt;/code&gt; or &lt;code&gt;'8bit'&lt;/code&gt; block-wise quantization — biggest speed lever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delete_original&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes the original download after splitting; halves disk usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layer_shards_saving_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Put shards on a different (faster/bigger) drive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;profiling_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logs time consumption per layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hf_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;For gated models (Llama, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prefetching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overlaps loading and compute (~10% gain); on by default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Common errors, decoded
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Actual cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MetadataIncompleteBuffer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Out of disk space. It's basically always this.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;401 Client Error... Repo is gated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pass &lt;code&gt;hf_token='...'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Asking to pad but the tokenizer does not have a padding token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;padding=False&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ValueError: max() arg is an empty sequence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;AutoModel&lt;/code&gt;, not a specific model class&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;Works on Apple Silicon only. Install &lt;a href="https://github.com/ml-explore/mlx" rel="noopener noreferrer"&gt;mlx&lt;/a&gt; plus torch, and make sure you're on native (not Rosetta) Python. Same code otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is it worth checking out?
&lt;/h2&gt;

&lt;p&gt;Depends entirely on which of these you are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip it if you want to chat with a big model
&lt;/h3&gt;

&lt;p&gt;This is the fantasy that draws people in, and it just doesn't work. Interactive chat needs ~20+ tokens/sec. AirLLM gives you seconds-to-minutes &lt;em&gt;per token&lt;/em&gt;. You will not be having a conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip it if you're doing serious volume
&lt;/h3&gt;

&lt;p&gt;Every token reads tens of GB off your SSD. Consumer NVMe drives are rated for a finite number of terabytes written; hammering one with continuous full-model reads and shard rewrites is not what it was designed for. Also, your machine will be effectively unusable while it runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's genuinely great for offline batch work
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is the real use case, and it's underrated.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The killer insight: the expensive part is &lt;em&gt;loading a layer&lt;/em&gt;, not &lt;em&gt;using it&lt;/em&gt;. So if you load Layer 1 and run &lt;strong&gt;50 prompts through it&lt;/strong&gt; before moving on, you amortize that cost 50 ways.&lt;/p&gt;

&lt;p&gt;One reported benchmark: &lt;strong&gt;35 s/token for a single prompt vs 5.3 s/token when batching 50&lt;/strong&gt; — a 6.6x improvement for free.&lt;/p&gt;

&lt;p&gt;So if you have 10,000 documents to classify overnight and no GPU budget, AirLLM is a legitimately reasonable tool. Latency doesn't matter when nobody's waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's great if you need &lt;em&gt;full precision&lt;/em&gt;, specifically
&lt;/h3&gt;

&lt;p&gt;Research on quantization effects, numerical reproducibility, evaluating a model as-published — cases where a 4-bit approximation defeats the point. AirLLM is close to the only way to do this on hardware you already own.&lt;/p&gt;




&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The claim is real.&lt;/strong&gt; 70B on 4GB VRAM, full precision, no tricks in the "lying" sense. The engineering is clever and the MoE expert-streaming work is legitimately impressive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The framing is the problem.&lt;/strong&gt; "Run 70B on a 4GB GPU" implies you get 70B-quality answers on cheap hardware. What you actually get is 70B-quality answers &lt;em&gt;eventually&lt;/em&gt; — at a pace measured in minutes per token, with your SSD as the engine and your GPU mostly idle.&lt;/p&gt;

&lt;p&gt;AirLLM didn't remove the cost of running a huge model. It &lt;strong&gt;moved&lt;/strong&gt; it — out of VRAM, into time and disk I/O. Whether that's a good trade depends entirely on whether you have more time than money.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/lyogavin/airllm" rel="noopener noreferrer"&gt;AirLLM on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Have you actually run this on real hardware? I'd love to hear your tokens/sec and your disk setup in the comments — the community numbers vary wildly and more data points would help.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>python</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
