<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>T KOLSTO · Tagged ash</title><link>https://t.kolsto.no/tags/ash/</link><description>Posts on technology, infrastructure, and startups by Thomas Kolstø.</description><generator>Hugo</generator><language>en</language><copyright>© 2010 - 2026 Thomas Kolstø</copyright><lastBuildDate>Sat, 30 May 2026 00:00:00 +0700</lastBuildDate><atom:link href="https://t.kolsto.no/tags/ash/" rel="self" type="application/rss+xml"/><image><url>https://t.kolsto.no/img/avatar.jpg</url><title>T KOLSTO · Tagged ash</title><link>https://t.kolsto.no/tags/ash/</link></image><item><title>The Ash AI Tax</title><link>https://t.kolsto.no/blog/ash-ai-tax/</link><pubDate>Sat, 30 May 2026 00:00:00 +0700</pubDate><guid>https://t.kolsto.no/blog/ash-ai-tax/</guid><dc:creator>Thomas Kolstø</dc:creator><category>ash</category><category>elixir</category><category>AI</category><category>LLM</category><description>I kept noticing my AI agent burned more tokens on our Ash code than on plain Phoenix. So I measured it: ~2.6x more on Claude, ~1.9x on Codex — building the exact same feature.</description><enclosure url="https://t.kolsto.no/img/ash-ai-tax-chart.jpg" length="53026" type="image/jpeg"/><content:encoded>&lt;p&gt;For months I harbored a nagging suspicion I couldn&amp;rsquo;t prove. Every time I pointed a coding agent at our Ash codebase, it consumed tokens noticeably faster than when working on plain Phoenix. I initially dismissed this as mere confirmation bias and filed it away.&lt;/p&gt;&#10;&lt;p&gt;I finally tested this by building the same feature against the passing test suite in two stacks—idiomatic Ash and plain Phoenix + Ecto—and having an agent build it five times in each. The result was bigger than my hunch:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Building an identical feature, Claude (Opus 4.7) used &lt;strong&gt;+162% more tokens&lt;/strong&gt; in Ash than in plain Ecto. Codex (GPT-5.5) used &lt;strong&gt;+90% more&lt;/strong&gt;. This effect showed up on both agents.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;img src="https://t.kolsto.no/img/ash-ai-tax-chart.jpg" alt="Bar chart: Claude used 1.76M tokens on Ash vs 672k on Ecto (&amp;#43;162%); Codex used 1.02M on Ash vs 533k on Ecto (&amp;#43;90%). 20 runs, all green." width="1200" height="630" srcset="https://t.kolsto.no/img/ash-ai-tax-chart_hu_6de352feda187a39.webp 720w, https://t.kolsto.no/img/ash-ai-tax-chart_hu_66e814372288085d.webp 1200w" sizes="(min-width: 1120px) 1040px, (min-width: 601px) calc(100vw - 80px), calc(100vw - 40px)" loading="eager" fetchpriority="high"&gt;&#10;&lt;p&gt;I&amp;rsquo;d have bet on 25%. It was closer to 2.6x.&lt;/p&gt;&#10;&lt;h2 id="why-this-is-worth-measuring"&gt;Why this is worth measuring&lt;/h2&gt;&#10;&lt;p&gt;We have crossed a line where most code is now written with an agent in the loop. Tokens are not free; they represent money, latency, and pressure on a finite context window. This means framework choice must account for a dimension nobody includes in comparison tables: &lt;strong&gt;how expensive is it for an AI to work in?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;This is an uncomfortable question because the answer may contradict everything that makes a framework pleasant for &lt;em&gt;humans&lt;/em&gt;. Ash is genuinely delightful for certain problems, but the core issue isn&amp;rsquo;t whether it&amp;rsquo;s good. It&amp;rsquo;s whether the feature that helps me costs the robot extra—and by how much.&lt;/p&gt;&#10;&lt;h2 id="the-experiment"&gt;The experiment&lt;/h2&gt;&#10;&lt;p&gt;I wanted a number I could defend, not a vibe. So I pinned everything I could.&lt;/p&gt;&#10;&lt;p&gt;The goal was building a small Support-ticket domain: a &lt;code&gt;Ticket&lt;/code&gt; with validations, a &lt;code&gt;Comment&lt;/code&gt; relationship, an assignee, custom &lt;code&gt;close&lt;/code&gt;/&lt;code&gt;reopen&lt;/code&gt; actions with state-transition guards, and authorization (assignee or admin can close; admin only can reopen). This provided enough surface area to exercise the parts of Ash people actually use, moving beyond simple CRUD.&lt;/p&gt;&#10;&lt;p&gt;The key was defining the contract in a plain Elixir module boundary (&lt;code&gt;Support.create_ticket/2&lt;/code&gt;, &lt;code&gt;Support.close_ticket/2&lt;/code&gt;, etc.) and writing one framework-agnostic ExUnit suite against it. This boundary is idiomatic for both sides—an Ash &lt;code&gt;code_interface&lt;/code&gt; and an Ecto context function expose the exact same six functions. Success meant the suite went green, the project compiled, and the linter was happy. No partial credit allowed.&lt;/p&gt;&#10;&lt;p&gt;The controls were strict:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Both stacks used the same agent, model, and prompt; only the framework differed.&lt;/li&gt;&#10;&lt;li&gt;Reasoning effort was pinned to &lt;code&gt;high&lt;/code&gt; for every run—identical across agents and frameworks. (Measuring whether the agent reasons more in Ash was a key goal, so this setting could not drift.)&lt;/li&gt;&#10;&lt;li&gt;The Ash side received its idiomatic context (&lt;code&gt;usage_rules&lt;/code&gt; AGENTS.md, all 1,556 lines), mimicking what a real Ash developer&amp;rsquo;s agent would get. The Ecto side required no special input as it is inherent to the model&amp;rsquo;s structure.&lt;/li&gt;&#10;&lt;li&gt;We ran five fresh checks per cell, and crucially, I personally verified green status after every run rather than relying on the agent&amp;rsquo;s confirmation.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I repeated this process using two different agents—Claude Code and Codex—because &amp;ldquo;it replicates across models&amp;rdquo; is what separates a finding from an anecdote.&lt;/p&gt;&#10;&lt;p&gt;The entire harness—including the specification, shared suite, both reference implementations, the runner, and raw per-run token logs—is open source for reproduction or inspection:&#10;&lt;a href="https://github.com/tkolsto/ash-ai-tax-bench"&gt;github.com/tkolsto/ash-ai-tax-bench&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="how-much-more-does-ash-cost-in-tokens"&gt;How much more does Ash cost in tokens?&lt;/h2&gt;&#10;&lt;p&gt;About 2× more — verified across 20 runs, every one reached green:&lt;/p&gt;&#10;&lt;section class="tk-table-scroll" aria-label="The Ash AI Tax — table 1" tabindex="0"&gt;&#10; &lt;table&gt;&#10; &lt;thead&gt;&#10; &lt;tr&gt;&#10; &lt;th scope="col"&gt;Agent&lt;/th&gt;&#10; &lt;th scope="col" style="text-align: right"&gt;Ecto (avg tokens)&lt;/th&gt;&#10; &lt;th scope="col" style="text-align: right"&gt;Ash (avg tokens)&lt;/th&gt;&#10; &lt;th scope="col" style="text-align: right"&gt;Ash tax&lt;/th&gt;&#10; &lt;th scope="col" style="text-align: center"&gt;Turns: Ecto → Ash&lt;/th&gt;&#10; &lt;/tr&gt;&#10; &lt;/thead&gt;&#10; &lt;tbody&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Claude (Opus 4.7)&lt;/td&gt;&#10; &lt;td style="text-align: right"&gt;671,886&lt;/td&gt;&#10; &lt;td style="text-align: right"&gt;1,758,599&lt;/td&gt;&#10; &lt;td style="text-align: right"&gt;&lt;strong&gt;+162%&lt;/strong&gt;&lt;/td&gt;&#10; &lt;td style="text-align: center"&gt;22.4 → 31.0&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;Codex (GPT-5.5)&lt;/td&gt;&#10; &lt;td style="text-align: right"&gt;533,001&lt;/td&gt;&#10; &lt;td style="text-align: right"&gt;1,015,218&lt;/td&gt;&#10; &lt;td style="text-align: right"&gt;&lt;strong&gt;+90%&lt;/strong&gt;&lt;/td&gt;&#10; &lt;td style="text-align: center"&gt;23.2 → 32.8&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;/tbody&gt;&#10; &lt;/table&gt;&#10;&lt;/section&gt;&#10;&lt;p&gt;A quick disclaimer: Per-run variance is high—Codex&amp;rsquo;s Ash runs, for example, ranged from 526k to 1.44M tokens—so treat these as rough estimates (&amp;ldquo;roughly 2x&amp;rdquo;) rather than precise percentages. However, the overall direction is rock solid: on neither agent did Ash come out cheaper. Furthermore, the deltas barely budged between my initial 3-run pilot and the full 5-run matrix (Claude went from 179% to 162%; Codex went from 85% to 90%), which is exactly what you want to see before trusting a number.&lt;/p&gt;&#10;&lt;p&gt;Both agents needed about 40% more &lt;em&gt;turns&lt;/em&gt; to get Ash green. This difference matters greatly, as it unravels the whole thing.&lt;/p&gt;&#10;&lt;h2 id="where-do-the-extra-tokens-actually-go"&gt;Where do the extra tokens actually go?&lt;/h2&gt;&#10;&lt;p&gt;The most interesting part is that it wasn&amp;rsquo;t what I expected. The issue isn&amp;rsquo;t that Ash code is bigger; the agents&amp;rsquo; diffs were comparable in size—sometimes even smaller. The cost isn&amp;rsquo;t in what the agent &lt;em&gt;writes&lt;/em&gt;, but in what it has to &lt;em&gt;understand&lt;/em&gt; before it can write anything correct.&lt;/p&gt;&#10;&lt;p&gt;I saw this concentration of effort in three places:&lt;/p&gt;&#10;&lt;p&gt;Context loading is a major hurdle. Frameworks like Ash require agents to pull in usage rules, resource definitions, and extensions for correct resources. Claude handles this poorly because it re-reads its cached context on every single turn. This means that a large starting context multiplied by many turns results in an exponential cost increase. That explains why Claude&amp;rsquo;s tax (+162%) significantly exceeds Codex&amp;rsquo;s (+90%): it is the same underlying phenomenon, just different token accounting.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Reasoning, not typing.&lt;/strong&gt; The agent spent significant effort thinking about Ash, determining which declarative knob produces the desired imperative behavior for a test. Ecto simply requires you to &amp;ldquo;write a query.&amp;rdquo; Ash, however, involves considering the action, change, validation, policy, and whether it will survive atomic execution.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;The &amp;ldquo;that&amp;rsquo;s not how Ash works&amp;rdquo; tax.&lt;/strong&gt; This is the retry loop, and I have receipts. Watching the reference build, the friction was rarely domain logic; it was bending the framework to specific contracts:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;A &lt;code&gt;code_interface&lt;/code&gt; named &lt;code&gt;create_ticket&lt;/code&gt; &lt;em&gt;collides&lt;/em&gt; with a hand-written &lt;code&gt;create_ticket/2&lt;/code&gt;. The solution involves defining internal interfaces and wrapping them.&lt;/li&gt;&#10;&lt;li&gt;The suite expects &lt;code&gt;get_ticket&lt;/code&gt; to return &lt;code&gt;{:error, :not_found}&lt;/code&gt;, but Ash returns a NotFound wrapped inside an &lt;code&gt;Ash.Error.Invalid&lt;/code&gt;, necessitating an error-normalization shim.&lt;/li&gt;&#10;&lt;li&gt;Reading the current &lt;code&gt;status&lt;/code&gt; to guard a transition triggers Ash 3&amp;rsquo;s &lt;code&gt;default_actions_require_atomic? true&lt;/code&gt;. The fix (&lt;code&gt;require_atomic? false&lt;/code&gt; plus a guard validation) is detailed in usage rules, but ignoring this results in baffling atomic-validation errors and recovery round trips.&lt;/li&gt;&#10;&lt;li&gt;Policies: Consecutive &lt;code&gt;authorize_if&lt;/code&gt; statements are &lt;strong&gt;OR&lt;/strong&gt;, not AND. Getting this mental model wrong causes authorization tests to fail in ways that look like logic bugs.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;None of this is hard &lt;em&gt;once you know it&lt;/em&gt;. That&amp;rsquo;s the point: it&amp;rsquo;s a large surface of structured knowledge, and every gap in the agent&amp;rsquo;s understanding costs tokens.&lt;/p&gt;&#10;&lt;h2 id="so-is-it-worth-it"&gt;So is it worth it?&lt;/h2&gt;&#10;&lt;p&gt;I need to be fair here. The tension is this: for a human, Ash often means writing less code; for an AI, it means understanding more before writing anything. These pull in opposite directions, and which one dominates depends on who—or what—is at the keyboard.&lt;/p&gt;&#10;&lt;p&gt;The tax buys real value: declarative actions, policies living next to their data, generated APIs, and a mountain of correctness you don&amp;rsquo;t have to hand-roll or maintain. On a long-lived system with a team, that leverage is worth far more than a pile of tokens. Furthermore, this tax is &lt;em&gt;front-loaded comprehension&lt;/em&gt;, not ongoing bloat—it’s the cost of the agent learning the framework&amp;rsquo;s shape, not a per-line penalty forever.&lt;/p&gt;&#10;&lt;p&gt;However, if you are spinning up something small, leaning hard on agents for speed, or watching your token bill, &amp;ldquo;2x the tokens, every feature&amp;rdquo; is a number that belongs in the decision, right next to all the reasons Ash is lovely.&lt;/p&gt;&#10;&lt;h2 id="how-to-shrink-the-ash-ai-tax"&gt;How to shrink the Ash AI tax&lt;/h2&gt;&#10;&lt;p&gt;The most expensive element is comprehension, but it&amp;rsquo;s also the easiest to fix: provide the agent with understanding rather than making it pay to rediscover it.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Invest in &lt;code&gt;usage_rules&lt;/code&gt;.&lt;/strong&gt; This is your highest-leverage move. The entire cost stems from the agent learning Ash&amp;rsquo;s structure; a current &lt;code&gt;usage_rules&lt;/code&gt; file preloads that shape. Keep this synced (Warning: the &lt;code&gt;usage_rules.sync&lt;/code&gt; tooling itself has sharp edges, costing me several iterations during testing).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Use &lt;code&gt;ash_ai&lt;/code&gt; / tidewave&lt;/strong&gt; to feed scoped, relevant context instead of forcing the agent to crawl your entire domain every turn.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Scope what you put in the window.&lt;/strong&gt; Because Claude re-reads the cache per turn, a bloated context is a recurring bill. Smaller, sharper inputs save money on every single interaction.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Prefer generators.&lt;/strong&gt; Tools like &lt;code&gt;mix ash.codegen&lt;/code&gt; and igniter installers absorb much of the boilerplate that the agent would otherwise have to reason through token by token.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="what-id-still-want-to-know"&gt;What I&amp;rsquo;d still want to know&lt;/h2&gt;&#10;&lt;p&gt;Remember this is just a snapshot: one task, two models. Model representation will only improve, so today&amp;rsquo;s results aren&amp;rsquo;t permanent. It remains unclear if larger, multi-feature tasks will amortize or compound comprehension cost. Also, note that I measured &lt;em&gt;processed tokens&lt;/em&gt;; because Claude bills cache reads cheaply, the dollar gap is smaller than the token gap—&amp;ldquo;2.6x the tokens&amp;rdquo; doesn&amp;rsquo;t mean &amp;ldquo;2.6x the bill.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;If you reproduce this and get a different number, please let me know. That’s what the repo is for.&lt;/p&gt;&#10;&lt;h2 id="the-thing-i-actually-took-away"&gt;The thing I actually took away&lt;/h2&gt;&#10;&lt;p&gt;As agents become the default way code gets written, my focus shifted from Ash&amp;rsquo;s specific token cost to something larger: &lt;strong&gt;how comprehensible is this framework to an AI?&lt;/strong&gt; This dimension—the ease with which a system can learn its shape in a fresh context window—is rapidly becoming a critical axis of design that we have barely started measuring.&lt;/p&gt;&#10;&lt;p&gt;The frameworks that will win the agent era might not be the most elegant for humans; they will be those whose structure is cheapest to &lt;em&gt;learn&lt;/em&gt;, over and over, by an AI. This is precisely the problem &lt;code&gt;usage_rules&lt;/code&gt; is quietly inventing solutions for.&lt;/p&gt;&#10;&lt;p&gt;Ash is paying a tax today. But with its commitment to usage-rules, it stands as one of the few frameworks actively building the mechanism that pays that comprehension debt back down.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;The benchmark, warts and all, is on &lt;a href="https://github.com/tkolsto/ash-ai-tax-bench"&gt;GitHub&lt;/a&gt;. Tell me where I&amp;rsquo;m wrong.&lt;/em&gt;&lt;/p&gt;&#10;</content:encoded></item></channel></rss>