<?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: Nate Voss</title>
    <description>The latest articles on DEV Community by Nate Voss (@natevoss).</description>
    <link>https://dev.to/natevoss</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3839442%2F8d858ab2-90a7-47dd-b9ee-fa8c73b19227.png</url>
      <title>DEV Community: Nate Voss</title>
      <link>https://dev.to/natevoss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/natevoss"/>
    <language>en</language>
    <item>
      <title>Structured Prompts Cut Token Waste 35-40%. Here's Where It Actually Matters.</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Wed, 27 May 2026 09:00:52 +0000</pubDate>
      <link>https://dev.to/natevoss/structured-prompts-cut-token-waste-35-40-heres-where-it-actually-matters-12hf</link>
      <guid>https://dev.to/natevoss/structured-prompts-cut-token-waste-35-40-heres-where-it-actually-matters-12hf</guid>
      <description>&lt;p&gt;One structured prompt format. Two identical reasoning tasks. Same model. Unstructured: 1,240 tokens. Structured (with explicit schema): 847 tokens. 32% reduction. That's real, repeatable, shows up in cost logs. But it's also the easy part.&lt;/p&gt;

&lt;p&gt;The harder part is knowing whether those saved tokens actually translate to better answers on YOUR task. And knowing when structure helps and when it's just overhead.&lt;/p&gt;

&lt;p&gt;I spent the last month running the same prompts against Claude Sonnet 4.6 in both forms: one with step by step natural language instructions, one with XML tags and explicit field definitions. Code generation tasks, reasoning tasks, multi step workflows. Here's what the patterns actually show.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unstructured Baseline
&lt;/h2&gt;

&lt;p&gt;When you send a model a request in plain English, the model has to infer the shape you want. It's flexible. It's also ambiguous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a function that validates user email addresses and returns helpful error messages.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model will deliver SOMETHING. Maybe a function with inline validation. Maybe a helper class. Maybe a regex comment. Maybe a full test suite because "helpful error messages" seemed like extra context worth expanding. You got an answer, but you didn't specify the answer format.&lt;/p&gt;

&lt;p&gt;Over five runs with Sonnet 4.6, the same unstructured prompt produced three different architectural shapes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Single regex based validator with a switch statement for errors&lt;/li&gt;
&lt;li&gt;Class based validator with a dedicated error handler&lt;/li&gt;
&lt;li&gt;Regex validator with a factory function for creating error objects&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All correct. None of them what I actually wanted (a single, composable validation function that returned structured errors as objects).&lt;/p&gt;

&lt;p&gt;Total tokens across five runs: 6,200. Average per run: 1,240.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Structured Version
&lt;/h2&gt;

&lt;p&gt;Same task, now with explicit format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a JavaScript function: validateEmail()

Requirements:
- Input: string (email address)
- Output: { valid: boolean, error: string | null }
- Implementation: regex-based validation only
- Error messages: return null if valid, specific error reason if invalid

Error categories:
- "missing_at": no @ symbol found
- "invalid_domain": domain lacks . or has no TLD
- "invalid_local": local part contains invalid characters

Return example:
{ valid: true, error: null }
{ valid: false, error: "invalid_domain" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Over five runs with the same model, every output had the same shape. No factory functions, no classes, no extra bells. It did exactly what was asked.&lt;/p&gt;

&lt;p&gt;Total tokens across five runs: 4,235. Average per run: 847.&lt;/p&gt;

&lt;p&gt;32% reduction. No ambiguity. Consistent shape meant I could pipe the output directly into a test harness without transformation.&lt;/p&gt;

&lt;p&gt;Here's what that actually looked like:&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;function&lt;/span&gt; &lt;span class="nf"&gt;validateEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&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;atIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&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;atIndex&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;valid&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;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;missing_at&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;atIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;domain&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;.&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;valid&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;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;invalid_domain&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;// Check for invalid characters in local part&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localPart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&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="nx"&gt;atIndex&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;invalidChars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&amp;gt;()&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;[&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;,.;:&lt;/span&gt;&lt;span class="se"&gt;\s]&lt;/span&gt;&lt;span class="sr"&gt;/&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;invalidChars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localPart&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;valid&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;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;invalid_local&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;valid&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;Every structured run produced this exact shape. Unstructured runs generated the same logic but wrapped it differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Less Than You Think
&lt;/h2&gt;

&lt;p&gt;Here's the tricky part: tokens aren't the full story.&lt;/p&gt;

&lt;p&gt;The unstructured versions were objectively MORE flexible. If I had asked for "write a function AND include a test harness," one of those three architectures would have made that trivial. The structured format was so locked down that asking for tests required a second prompt.&lt;/p&gt;

&lt;p&gt;The benchmark friendly metric (tokens saved) is real. The useful metric (does this output directly feed my pipeline?) is context specific. Different answers, different weights for different tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Structure Actually Wins
&lt;/h2&gt;

&lt;p&gt;Code generation tasks: structure wins hard. You have a format spec. You want the model to follow it. Tokens drop, consistency rises.&lt;/p&gt;

&lt;p&gt;Running the same comparison on five reasoning tasks (writing essays, analyzing text, brainstorming), the token savings were still there (29% average), but the quality tradeoff appeared. Structured prompts locked the reasoning into tighter paths. Some essays came out more formulaic. Not worse, just more boundaried.&lt;/p&gt;

&lt;p&gt;The model hit a schema compliance target instead of exploring the actual reasoning space.&lt;/p&gt;

&lt;p&gt;For code: schema compliance IS the target. For reasoning: sometimes the messiness is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Math (Real Numbers)
&lt;/h2&gt;

&lt;p&gt;Using current pricing (Sonnet 4.6 input at $3/1M, output at $15/1M), average input tokens 2,000, average output 800:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unstructured approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: 2,000 tokens × ($3/1M) = $0.000006&lt;/li&gt;
&lt;li&gt;Output: 1,200 tokens × ($15/1M) = $0.000018&lt;/li&gt;
&lt;li&gt;Per call: $0.000024&lt;/li&gt;
&lt;li&gt;100 calls: $0.0024&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Structured approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: 2,000 tokens × ($3/1M) = $0.000006&lt;/li&gt;
&lt;li&gt;Output: 800 tokens × ($15/1M) = $0.000012&lt;/li&gt;
&lt;li&gt;Per call: $0.000018&lt;/li&gt;
&lt;li&gt;100 calls: $0.0018&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Difference: $0.0006 per 100 calls. On pricing, it's noise. On latency (fewer output tokens = faster), it matters more.&lt;/p&gt;

&lt;p&gt;If your task outputs 4,000 tokens regularly, suddenly the math shifts. Structured formats that reduce 4,000 token outputs by 30% actually save something you notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern Recognition Angle
&lt;/h2&gt;

&lt;p&gt;What's interesting is what the output patterns reveal about how models parse instructions.&lt;/p&gt;

&lt;p&gt;Models trained on massive code datasets have seen thousands of function specifications. When you send a structured spec (name, input type, output type, constraints), you're activating pattern recognition pathways the model has seen before. It copies the shape. Fast, consistent, fewer tokens.&lt;/p&gt;

&lt;p&gt;When you send natural language, the model has to build context from scratch. It's slower, fuzzier, more creative. For code, that's overhead. For reasoning, that's sometimes the whole point.&lt;/p&gt;

&lt;p&gt;The models aren't "reasoning through" the unstructured prompt. They're doing pattern matching on a less constrained pattern set. Which is fine. Just know that's what's happening. The structured version isn't necessarily smarter, it's just aimed at a narrower target.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Move
&lt;/h2&gt;

&lt;p&gt;If you're optimizing cost on code generation at scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use structured formats (XML or JSON schema)&lt;/li&gt;
&lt;li&gt;Pre specify output shape and type constraints&lt;/li&gt;
&lt;li&gt;Accept that consistency comes at the cost of flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're working on reasoning or analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test both formats on your actual task&lt;/li&gt;
&lt;li&gt;Don't assume the token savings mean better output&lt;/li&gt;
&lt;li&gt;Watch the quality delta across 5 10 runs, not the benchmark&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The people telling you "always structure your prompts" are right about code. They're also copying advice from a code heavy community. Test it on your task. The benchmark lift doesn't predict real utility. Your data does.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #ai #tutorial #javascript #optimization&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>optimization</category>
    </item>
    <item>
      <title>3 Things I Learned About Holding Context Through Long Debugging Sessions</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Mon, 18 May 2026 09:39:41 +0000</pubDate>
      <link>https://dev.to/natevoss/3-things-i-learned-about-holding-context-through-long-debugging-sessions-1p7g</link>
      <guid>https://dev.to/natevoss/3-things-i-learned-about-holding-context-through-long-debugging-sessions-1p7g</guid>
      <description>&lt;p&gt;You're four turns into debugging something with an LLM. The model just asked a clarifying question you answered two exchanges ago. You paste the error trace again. You repeat the code snippet. You restate the prior fix you attempted.&lt;/p&gt;

&lt;p&gt;The model has continuity. Your token bill does not.&lt;/p&gt;

&lt;p&gt;This is the cost of context collapse. And it's everywhere in production code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thing 1: Context is expensive, and repetition is the silent killer
&lt;/h2&gt;

&lt;p&gt;Every time you re-paste the error trace, the stack trace, the code snippet, the prior fix attempt, you're paying for tokens you already paid for.&lt;/p&gt;

&lt;p&gt;If a typical debugging session is ten turns and each turn repeats sixty percent of prior context, you're paying token costs for that sixty percent nine times. Do that ten times a day and the math starts to sting.&lt;/p&gt;

&lt;p&gt;Here's what it looks like:&lt;/p&gt;

&lt;p&gt;A standard debugging context (error, code, prior attempts) is about two thousand tokens. If you repeat it eight times across a session, that's sixteen thousand tokens just to restate the same problem.&lt;/p&gt;

&lt;p&gt;Same session with optimized context reuse. Two thousand base. Three hundred per turn average for new information. Twenty-four hundred per turn maximum. Across eight turns, you're at around four thousand four hundred tokens total.&lt;/p&gt;

&lt;p&gt;The difference is eleven thousand six hundred tokens. Doesn't sound like much per session. Compounds fast when this is your daily work.&lt;/p&gt;

&lt;p&gt;The obvious fix: don't repeat yourself. But obvious and easy are different things. Most code I see just resubmits everything. It works. It costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thing 2: Prompt caching changes how you structure the conversation
&lt;/h2&gt;

&lt;p&gt;Prompt caching lets you mark parts of your prompt as stable. The system instructions, the error trace, the code snippet. You cache them once. Subsequent calls reuse that cache without repaying token cost.&lt;/p&gt;

&lt;p&gt;The insight is not just about money. It's about how you architect the prompting workflow itself.&lt;/p&gt;

&lt;p&gt;Instead of flattening every message with repeated context, you structure it as layers.&lt;/p&gt;

&lt;p&gt;First call: establish the cache with stable context, get your initial response.&lt;/p&gt;

&lt;p&gt;Subsequent calls: send only new information. The model still has full context because the cache holds the stable parts.&lt;/p&gt;

&lt;p&gt;Here's what that looks like:&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;Anthropic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@anthropic-ai/sdk&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="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&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;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a debugging assistant. Help diagnose and fix code issues. Be precise, suggest concrete changes, ask clarifying questions if needed.`&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;stableContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
## Error Context
Stack trace:
TypeError: Cannot read property 'id' of undefined
 at getUserData (./src/services/user.js:45)
 at Object.&amp;lt;anonymous&amp;gt; (./src/index.js:12)

## Code
function getUserData(user) {
 return {
 id: user.id,
 name: user.name,
 email: user.email
 };
}

## Prior Attempts
1. Added null check: if (!user) return null
2. Logged user object before line 45
3. Confirmed user is undefined when function called
`&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;debuggingSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Turn 1: Initial diagnosis (cache established)&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response1&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-opus-4-7&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;system&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stableContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;cache_control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ephemeral&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="p"&gt;],&lt;/span&gt;
 &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Why is user undefined here? The function is called from index.js line 12.&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="p"&gt;});&lt;/span&gt;

 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Turn 1:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache created:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache_creation_input_tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="c1"&gt;// Turn 2: Followup (reuses cache)&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-opus-4-7&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;system&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stableContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;cache_control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ephemeral&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="p"&gt;],&lt;/span&gt;
 &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Why is user undefined here? The function is called from index.js line 12.&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;text&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I checked the caller. It&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;s passing an empty object. Should I validate the shape?&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="p"&gt;});&lt;/span&gt;

 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Turn 2:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache read from:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache_read_input_tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New input tokens:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;debuggingSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turn 1 pays full price. Look at the usage object. &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; shows what got cached.&lt;/p&gt;

&lt;p&gt;Turn 2 reuses it. &lt;code&gt;cache_read_input_tokens&lt;/code&gt; shows tokens pulled from the cache. Those cost about ninety percent less than new input tokens.&lt;/p&gt;

&lt;p&gt;You're holding the context without repeating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thing 3: The pit: assuming the cache survives beyond the session
&lt;/h2&gt;

&lt;p&gt;This is the mistake I see most. Developers set up caching and assume it persists. It doesn't.&lt;/p&gt;

&lt;p&gt;Cache is scoped to the session. Close the client connection, the cache is gone. Next time you run the script, cold cache. Full price.&lt;/p&gt;

&lt;p&gt;The mental model matters. Caching is for single-session optimization. If you need context to travel across sessions (the user's project state, the error they're debugging this week), that goes in your app's state or database, not the LLM cache.&lt;/p&gt;

&lt;p&gt;Another trap: stale cache. If the code changed or the error trace got updated, the cache doesn't know. You're debugging the wrong code. If stable context is external or user-provided, include a hash or timestamp. When it changes, cache misses. Rebuild.&lt;/p&gt;

&lt;p&gt;The guardrail is simple. Manual invalidation if anything in the stable context is live data.&lt;/p&gt;




&lt;p&gt;Multi-step debugging sessions are where this pays off. Structure it once. Run turns. Let the cache expire naturally when the session ends.&lt;/p&gt;

&lt;p&gt;You're holding the thread. The model has continuity. Your costs don't multiply.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>When Code Is Free, Intention Is the Craft</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Fri, 15 May 2026 08:38:38 +0000</pubDate>
      <link>https://dev.to/natevoss/when-code-is-free-intention-is-the-craft-4jgi</link>
      <guid>https://dev.to/natevoss/when-code-is-free-intention-is-the-craft-4jgi</guid>
      <description>&lt;p&gt;I spent a week last month trying to decide whether to reach for a component library or build something small and custom. A standard problem. Normally I'd make the call in an afternoon. Weigh the time cost against the maintenance debt, measure the friction points in the UI, make the trade-off, move on.&lt;/p&gt;

&lt;p&gt;This time I kept stalling. Not because I couldn't generate either solution. A good prompt gets me either path in an hour. The component library is documented. The custom build is straightforward. The actual cost of producing code has stopped being the constraint.&lt;/p&gt;

&lt;p&gt;I was stalling because I had to figure out what I actually wanted. Not what was faster. Not what had fewer dependencies. What did I want the software to do, and what was I willing to trade to get there.&lt;/p&gt;

&lt;p&gt;That's craft now. Not the code. The intention.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Execution Stopped Being the Bottleneck
&lt;/h2&gt;

&lt;p&gt;For a long time, "software craftsmanship" meant writing code that was clean, maintainable, elegant to read. It was the difference between someone who shipped sloppy work and someone who wrote code that would last, code that other people could inherit without cursing your name six months later.&lt;/p&gt;

&lt;p&gt;That still matters. But it's not the bottleneck anymore.&lt;/p&gt;

&lt;p&gt;The bottleneck is deciding what deserves to exist.&lt;/p&gt;

&lt;p&gt;You can generate a component library integration in forty minutes. You can generate a custom component in fifty. The time delta is noise now. What you actually spend your time on is whether the custom version is worth the ongoing maintenance. Whether the library forces a mental model that's actually wrong for this problem. What breaks quietly if you choose wrong.&lt;/p&gt;

&lt;p&gt;These are intention questions, not craftsmanship questions.&lt;/p&gt;

&lt;p&gt;When I was learning, good developers were distinguished by their ability to write correct, maintainable code at speed. That was a real skill. You had to hold the whole system in your head and translate it into code that others could read and modify. Now the system-holding is still necessary, but the translation is automated. The bottleneck has moved upstream.&lt;/p&gt;

&lt;p&gt;A junior developer can now generate technically perfect code. It will pass tests. It will follow style rules. It will do what you asked. But if you asked the wrong question, it will do the wrong thing perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thing You Can't Automate
&lt;/h2&gt;

&lt;p&gt;I've noticed it in code reviews. A PR comes in that's technically flawless. Tests passing, patterns followed, everything the linter wants. But I find myself asking: why did you choose this approach? Not because something's broken. Because I can't see the reasoning in the code anymore. The code itself no longer carries the weight of difficult choices. It's too clean.&lt;/p&gt;

&lt;p&gt;The uncomfortable truth is that sometimes the right answer is inefficient. Sometimes it's weird. Sometimes it violates a rule because you've discovered a specific constraint that makes the rule wrong in this case. Those decisions used to show up in the code, visible to anyone reading it, proof that someone was thinking. Now they don't. The code can be flawless and thoughtless simultaneously.&lt;/p&gt;

&lt;p&gt;This changes what junior developers should actually be learning. Not "write clean code fast." That's commoditized now. It's "know when to break your own rules and why." It's "understand the system deeply enough to know when a pattern doesn't fit." It's "ask the right question before you ask the code to answer it."&lt;/p&gt;

&lt;p&gt;It also changes what you should be optimizing for in your own work. I used to try to get faster at writing code. That was the right goal when code-speed was the binding constraint. Now I'm trying to get better at asking questions. At running thought experiments before I open a file. At noticing when I'm about to solve the wrong problem elegantly instead of the right problem messily.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thing That Doesn't Work Anymore
&lt;/h2&gt;

&lt;p&gt;The weird part is that this should make software better. If everyone's spending more time thinking and less time typing, we should ship fewer bad decisions. But I'm not sure we do. The cheaper the code becomes, the more of it we generate. The more we build things that don't need to exist. The more we ship because we can, because the friction of shipping has dropped below the threshold of "maybe this isn't worth doing."&lt;/p&gt;

&lt;p&gt;The constraint that used to be time-to-ship, and thus made you think hard before you shipped, has dissolved. Now the constraint is intention. Do you actually think this matters? Are you shipping it because it solves a real problem or because it was fun to build?&lt;/p&gt;

&lt;p&gt;Those are harder questions. They don't have right answers waiting to be discovered. They require you to know something about what you're trying to do and why, and then to hold that intention steady while you build it. They require discipline about the scope of your own attention.&lt;/p&gt;

&lt;p&gt;That's the craft now. The thinking before the code. The discipline to notice when you're building something because you can, not because you should.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 rules I added to my CLAUDE.md after burning a full day on a Tiptap editor</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Wed, 13 May 2026 08:33:47 +0000</pubDate>
      <link>https://dev.to/natevoss/5-rules-i-added-to-my-claudemd-after-burning-a-full-day-on-a-tiptap-editor-4efm</link>
      <guid>https://dev.to/natevoss/5-rules-i-added-to-my-claudemd-after-burning-a-full-day-on-a-tiptap-editor-4efm</guid>
      <description>&lt;p&gt;I spent a full day fighting a Tiptap editor through CDP to post short notes from a side project of mine. Rewrote the injection script four times. Mapped out ProseMirror's transaction API. Worked around &lt;code&gt;isTrusted&lt;/code&gt; checks. Got it to render the text. Got the publish button to enable. Got a 200 back. Watched the post not actually appear because the editor's internal state hadn't been updated through a real input event.&lt;/p&gt;

&lt;p&gt;Then I searched npm. There was a maintained TypeScript package, updated March 2026, that did the whole thing in three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPost&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="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No browser. No CDP. No Tiptap. The problem was already solved. I just hadn't searched first.&lt;/p&gt;

&lt;p&gt;That day became the first rule in my CLAUDE.md. Then four more, because the underlying mistake had layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;strong&gt;Search before building. Always.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Any time the AI is about to write browser automation, reverse-engineer a private API, or fight a platform's DOM, it has to pause and search first. Targets in order: GitHub (&lt;code&gt;site:github.com &amp;lt;platform&amp;gt; api typescript&lt;/code&gt;), npm (&lt;code&gt;npm search &amp;lt;platform&amp;gt;&lt;/code&gt;), and MCP server registries. Someone has almost always fought this before. The 20-minute install beats the day of debugging every single time, and the only way I learned that was by paying the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;strong&gt;Verify the library can write, not just read.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This one cost me a second smaller hole. Half the packages you find for a given platform are read-only scrapers. They'll happily list posts and pull comments and do nothing you actually need. Before I let the AI commit to a dependency, the rule is: confirm there's a &lt;code&gt;publish&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, or &lt;code&gt;submit&lt;/code&gt; method in the public API. If the README only shows GET-style examples, keep searching.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;strong&gt;Check that the package is actually maintained.&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;last commit: &amp;lt; 6 months ago
open issues: not a graveyard
latest release: matches the README examples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stale package is worse than no package, because it lulls you into thinking the problem is solved when the auth flow it relies on broke six months ago. Six months of commit activity is the cheap heuristic I encoded. It's not perfect but it filters out the abandoned ones in two clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;strong&gt;Prefer libraries with a stable auth mechanism.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The Tiptap package I eventually used authenticated with a cookie token I pasted once. The fragile alternative is anything that scripts a login form, because login forms get reworked, get CAPTCHAs added, get extra CSRF steps. Cookie or token auth survives UI redesigns. Form-driven login dies the next time the platform ships a new sign-in page. So the rule says: if the only auth path is DOM login, treat the library as a last resort.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;strong&gt;When nothing exists, document what you searched.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the rule that keeps future-me from repeating present-me's mistake. If a real search genuinely turns up nothing and custom automation is justified, the AI has to write down what queries it ran and what registries it checked, in a comment at the top of the new file. Otherwise three weeks later I'll be in the same spot, wondering if anything has shipped since, and I'll re-do the same fruitless search instead of trusting the prior negative result. Document the dead end and the next session inherits it.&lt;/p&gt;




&lt;p&gt;These five rules pair with a Documentation-First rule I keep alongside them. Search-Before-Building finds the tool. Documentation-First makes sure the AI actually reads the README instead of guessing at the API shape from the package name. The two together have killed almost every "let's just script the browser" instinct my AI used to have by default.&lt;/p&gt;

&lt;p&gt;The shift in how my AI proposes solutions has been noticeable. Where it used to reach for Puppeteer or a CDP snippet within the first message, it now opens with a search step and reports back with two or three candidate libraries before writing a single line of new code. The first time it said "I checked npm and GitHub, nothing maintained exists for this, here's what I searched" before falling back to custom code, I knew the rule had landed.&lt;/p&gt;

&lt;p&gt;If you want to copy these into your own setup, here's the format that works in CLAUDE.md, cursor rules, or copilot instructions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before writing custom browser automation, API reverse-engineering, or DOM workarounds, search GitHub, npm, and MCP server registries for an existing library first. Only build custom if a real search returns nothing, and if you do, document the queries you ran so the next session doesn't repeat them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add it once, save yourself the next version of this same day.&lt;/p&gt;

&lt;p&gt;Tags: #claudecode #ai #productivity #devtools #javascript&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>How I Cut My API Bill in Half Without Understanding What I Was Doing</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Mon, 11 May 2026 08:58:11 +0000</pubDate>
      <link>https://dev.to/natevoss/how-i-cut-my-api-bill-in-half-without-understanding-what-i-was-doing-2dj8</link>
      <guid>https://dev.to/natevoss/how-i-cut-my-api-bill-in-half-without-understanding-what-i-was-doing-2dj8</guid>
      <description>&lt;p&gt;How many of your API calls are re-processing the same instructions?&lt;/p&gt;

&lt;p&gt;I couldn't answer that question either. I had this 4,000-token context block I was shipping to Claude Opus 4.7 with every request. Cost me about a cent per call. I kept moving on because it worked. One day I looked at the math. $300 a month. And realized I was wasting money I didn't need to waste.&lt;/p&gt;

&lt;p&gt;I started looking at what was actually changing per request and what stayed the same. System prompt? Same. Instruction block? Same. Company style guide? Same. User question? Different.&lt;/p&gt;

&lt;p&gt;That's when prompt caching clicked. But here's the thing: I didn't really understand why I was doing it until I had to explain it to someone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question You Can't Answer Without Implementing It
&lt;/h2&gt;

&lt;p&gt;Prompt caching seems simple: mark some blocks of your request as "cache this," and Claude reads them from cache on the next request. 90% cost reduction on cached tokens.&lt;/p&gt;

&lt;p&gt;But it only works if you truly understand what in your request is static and what's dynamic. A lot of developers think they know the difference. They don't. Not until they try to cache.&lt;/p&gt;

&lt;p&gt;I'll show you the code in a second. But first: the hard part isn't the code. It's the thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Caching Matters
&lt;/h2&gt;

&lt;p&gt;The obvious reason: cost. A 2,000-token system prompt that you send 10,000 times a month costs real money. Caching cuts that to nearly nothing.&lt;/p&gt;

&lt;p&gt;The actual reason you should care: caching forces you to author your prompts consciously. Either your instructions are truly reusable. In which case, cache them. Or they're not. In which case, you've got a mess in your code.&lt;/p&gt;

&lt;p&gt;Here's what happens when you try to cache without thinking: you'll put a user ID in the system prompt. Or today's date. Or a flag that changes per request. Then you'll wonder why the cache never hits. You'll add logging. You'll go three layers deep into debugging. And then you'll realize: you never actually separated "stable instruction" from "user input."&lt;/p&gt;

&lt;p&gt;That moment is worth the whole exercise, regardless of the cost savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&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;Anthropic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@anthropic-ai/sdk&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="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ANTHROPIC_API_KEY&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;STYLE_GUIDE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
# Code Style Standards

## Naming Conventions
- Variables: camelCase
- Classes: PascalCase
- Constants: UPPER_SNAKE_CASE
- Private methods: _leading underscore

## Comments
- Comment the why, not the what
- Every public function gets a JSDoc block
...
[imagine 3,000 tokens of actual guidelines]
`&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;reviewCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSubmittedCode&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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-opus-4-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;system&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You are a code reviewer. Apply the style guide strictly. Be direct and specific.&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="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;STYLE_GUIDE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;cache_control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ephemeral&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="p"&gt;],&lt;/span&gt;
 &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Review this code:\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userSubmittedCode&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="p"&gt;},&lt;/span&gt;
 &lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;text&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;That &lt;code&gt;cache_control: { type: "ephemeral" }&lt;/code&gt; tells Claude to cache the STYLE_GUIDE block. The first call pays the cost of processing it. Every call in the next 5 minutes reads from cache.&lt;/p&gt;

&lt;p&gt;You can verify the hit in the response:&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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({...});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache_creation_input_tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// first request&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache_read_input_tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// subsequent requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;cache_read_input_tokens &amp;gt; 0&lt;/code&gt;, the cache worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gotcha That Catches Everyone
&lt;/h2&gt;

&lt;p&gt;Cache hits depend on exact byte-for-byte matching. Exact.&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="c1"&gt;// Cache HIT&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;guide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a code reviewer...`&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;guide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a code reviewer...`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Cache MISS (extra space)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;guide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a code reviewer...`&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;guide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are a code reviewer... `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// trailing space&lt;/span&gt;

&lt;span class="c1"&gt;// Cache MISS (different model)&lt;/span&gt;
&lt;span class="c1"&gt;// change from opus-4-7 to sonnet-4-6&lt;/span&gt;

&lt;span class="c1"&gt;// Cache MISS (you vary user messages before the cached block)&lt;/span&gt;
&lt;span class="c1"&gt;// Cache key changes every time the message history changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where understanding matters. You can't just scatter &lt;code&gt;cache_control&lt;/code&gt; through your code and expect it to work. You have to commit to treating those blocks as constants.&lt;/p&gt;

&lt;p&gt;A lot of developers find this frustrating. I found it clarifying. If I'm shipping code where the instructions change per request, the cache can't help. So either: rewrite the instructions to not change, or remove the cache. Either way, I'm forced to make a choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Caching Actually Wins
&lt;/h2&gt;

&lt;p&gt;Batch processing with consistent context. Processing 100 files with the same analysis rules.&lt;/p&gt;

&lt;p&gt;Customer support: load the knowledge base once, every user question reuses it.&lt;/p&gt;

&lt;p&gt;Long-form analysis: load a document, ask 20 follow-up questions against it. Only the first question pays for the document.&lt;/p&gt;

&lt;p&gt;Code generation with a stable system persona and reference library.&lt;/p&gt;

&lt;p&gt;Anything where you're making multiple requests with the same expensive-to-process input.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thing Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;I started caching because I wanted to cut costs. I kept caching because it made my code clearer.&lt;/p&gt;

&lt;p&gt;When you have to decide "is this instruction static or dynamic?" you start thinking about what your prompts actually are. You stop cargo-culting patterns. You own the code because you can explain it.&lt;/p&gt;

&lt;p&gt;That matters more than the 85% cost reduction.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Suggested tags:&lt;/strong&gt; ai, tutorial, javascript, beginners&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Leverage Shift: Why Infrastructure Cost Doesn't Matter Anymore</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Fri, 08 May 2026 07:15:44 +0000</pubDate>
      <link>https://dev.to/natevoss/the-leverage-shift-why-infrastructure-cost-doesnt-matter-anymore-3o1m</link>
      <guid>https://dev.to/natevoss/the-leverage-shift-why-infrastructure-cost-doesnt-matter-anymore-3o1m</guid>
      <description>&lt;p&gt;Last month I built a feature that would've consumed my monthly API budget three years ago. It involved processing 50,000 tokens of context, running chains of prompts, error recovery, retries. I spent maybe four dollars. Not 400. Not "significant." Four.&lt;/p&gt;

&lt;p&gt;That casually happened during a normal Tuesday build. I wasn't optimizing for cost. I wasn't watching the meter. I was shipping clarity. Breaking down a complex decision into smaller, dumber steps. And the economics of doing that were so cheap that they didn't show up on my radar anymore.&lt;/p&gt;

&lt;p&gt;This is the quiet shift nobody talks about when they talk about AI. It's not "AI is now viable for startups". Everyone's saying that, and they're right. It's deeper: the economic game of building and shipping software has fundamentally changed shape, and most people are still playing chess with the old board.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Claude 3.5 Sonnet costs $3 per million input tokens, $15 per million output tokens. GPT-4o is $5/$15. A context window that can hold a small novel costs pennies to run inference on.&lt;/p&gt;

&lt;p&gt;A solo developer building a feature that makes 10,000 API calls per day runs maybe $150/month. That's the cost of a coffee subscription. Compare that to the cost of hiring one mid-level engineer to ship the same thing, or the infrastructure capital you needed in 2015 to run equivalent workloads yourself. Tens of thousands in hardware, plus the engineering time to maintain it.&lt;/p&gt;

&lt;p&gt;But the real shift isn't in the absolute cost. It's in &lt;em&gt;who gets to ship&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moat problem
&lt;/h2&gt;

&lt;p&gt;Every "AI-first startup" shipping right now is built on this exact cost collapse. Someone got VC funding, hired a team, built something that calls Claude, made it prettier with a design system, and shipped it. The business model is usually "we mark up the API calls." Which means. And I'm not being harsh. They're playing with a moat they don't own.&lt;/p&gt;

&lt;p&gt;Your moat isn't the model. It isn't the tokens. It isn't the prompt. It's something else, or it doesn't exist.&lt;/p&gt;

&lt;p&gt;I've noticed this building systems that rely on language models. The feature that matters isn't "we call Claude to generate content." That's technical infrastructure anyone can replicate in an afternoon. The feature that matters is the specific way the system engages across platforms, the calibration of how much to reply versus broadcast, the calendar that knows when to rest. That only works because someone cares enough to refine it for 18 months and measure what actually lands with an audience. The API calls are the easy part.&lt;/p&gt;

&lt;p&gt;You can build that infrastructure for four dollars a month. You cannot buy the other layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source vs the API
&lt;/h2&gt;

&lt;p&gt;This is where the calculation gets interesting. Self-hosting Llama 2 on a p3.8xlarge costs roughly $12/hour. For a low-volume feature (maybe 1,000 tokens/day), that's economically indefensible. You're paying for idle compute. For high-volume (millions of tokens/month), it pencils out.&lt;/p&gt;

&lt;p&gt;But "pencils out" ignores the hidden costs: maintenance, inference optimization, managing VRAM, handling failures, updating models. And it ignores the opportunity cost: that's your engineering time, not shipping the actual feature.&lt;/p&gt;

&lt;p&gt;The shift is that the crossover point has moved. Five years ago, building anything non-trivial in production meant: evaluate open-source models, find one that works, self-host it, hire someone to maintain it. The API was expensive; the infrastructure was cheap (because you didn't pay for idle time).&lt;/p&gt;

&lt;p&gt;Now the API is cheap enough that most solo projects shouldn't self-host. You're not choosing between "pay the API vendor" and "own our infrastructure." You're choosing between "pay $200/month" and "pay $50,000 in engineering for something that breaks in production and costs $3,000/month to run."&lt;/p&gt;

&lt;p&gt;There are exceptions. If you're running language models at hyperscaler volume (billions of tokens/month), self-hosting with cheaper open models becomes non-negotiable. But that's not the constraint on most projects. And even then, you're still paying for compute. You're just deciding to own the infrastructure instead of outsourcing the billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost: clarity
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't expect: cheaper infrastructure doesn't make the problems simpler. It moves them.&lt;/p&gt;

&lt;p&gt;The cost of building with AI used to be economic: can I afford to make this call? Now it's cognitive. Can I write the logic clearly enough that the model does what I actually need? Can I debug why this worked yesterday and not today? Can I handle the failure case when the model hallucinates?&lt;/p&gt;

&lt;p&gt;I spent a week recently on a single decision-making routine. The model was generating great output but missing the signal I needed buried in the analysis. I kept adding context, more examples, longer explanations. Finally hit a token budget and had to cut 70% of what I'd written. The version that worked. The one that was clear instead of thorough. Was the one I'd almost deleted.&lt;/p&gt;

&lt;p&gt;The use has moved from "can you afford compute?" to "can you think clearly?" And that's actually a more interesting gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for solo builders
&lt;/h2&gt;

&lt;p&gt;You can now build the infrastructure of a Series A company, alone, for the cost of a Spotify subscription. That's real. Not metaphorically. Literally. The cloud bills are negligible. The engineering is finite.&lt;/p&gt;

&lt;p&gt;What you can't do is own a moat you didn't build. You can't ship a wrapper and expect market gravity to solve the rest. The people winning with AI right now aren't winning because they found a good model. They're winning because they found a real problem and got ruthlessly specific about solving it.&lt;/p&gt;

&lt;p&gt;The gate isn't capital anymore. The gate is clarity. Do you know what you're building? Do you know it better than anyone else? Can you measure whether it's working? Can you refine it based on signal instead of hope?&lt;/p&gt;

&lt;p&gt;That's the use. Infrastructure cost collapsing just made it possible to do alone.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Folks, need some feedback on this: https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Thu, 07 May 2026 10:24:08 +0000</pubDate>
      <link>https://dev.to/natevoss/folks-need-some-feedback-on-this-3g0c</link>
      <guid>https://dev.to/natevoss/folks-need-some-feedback-on-this-3g0c</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i" class="crayons-story__hidden-navigation-link"&gt;I wrote a rule after Claude got "is X built?" wrong 4 times. Looking for failure modes.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/natevoss" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3839442%2F8d858ab2-90a7-47dd-b9ee-fa8c73b19227.png" alt="natevoss profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/natevoss" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nate Voss
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nate Voss
                
              
              &lt;div id="story-author-preview-content-3620934" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/natevoss" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3839442%2F8d858ab2-90a7-47dd-b9ee-fa8c73b19227.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nate Voss&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 6&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i" id="article-link-3620934"&gt;
          I wrote a rule after Claude got "is X built?" wrong 4 times. Looking for failure modes.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/claude"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;claude&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I wrote a rule after Claude got "is X built?" wrong 4 times. Looking for failure modes.</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Wed, 06 May 2026 12:03:25 +0000</pubDate>
      <link>https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i</link>
      <guid>https://dev.to/natevoss/i-wrote-a-rule-after-claude-got-is-x-built-wrong-4-times-looking-for-failure-modes-2f3i</guid>
      <description>&lt;p&gt;I wrote a rule for AI coding agents two days ago. It is untested in production sessions. I am posting it here to find its failure modes faster than I would by waiting for my own future mistakes to surface them.&lt;/p&gt;

&lt;p&gt;The rule is below first. Story and reasoning after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pre-Build Existence Audit Rule (v1, structural verification)

Status: Untested in production sessions. Test on a new project for 2-3 weeks
before considering global rollout.

Before claiming "feature X is not built / not implemented / missing":

1. Map
   rg -li "&amp;lt;keyword&amp;gt;" .                            # project repo
   rg -li "&amp;lt;keyword&amp;gt;" ~/.claude/projects/*/memory/ # agent memory
   If either &amp;gt;5 files match, use the file list to scope which to read.

2. Structural footprint scan (NOT just synonyms)
   Identify architectural invariants this feature class would require:
   - Integration/API → router definitions, endpoint registrations,
     plugin tool lists
   - Data → schema files, migrations, type definitions, persisted-entity fields
   - Background → cron entries, queue handlers, scheduled job registrations
   - Cross-service → service registry, infra config, IPC handlers
   - Memory/decisions → project_*.md files documenting prior shipment

   Stack discipline: footprints must be stack-appropriate. If unsure which
   architectural pattern applies, list 2-3 plausible alternatives
   (REST/GraphQL/RPC; cron/queue/webhook) and search each. Wrong-ontology
   audits feel rigorous but miss truth.

   Grep each invariant. If ANY return matches, "not built" is contradicted
   until you've read those matches.

3. Epistemic categorization. Label each match as ONE of:
   - Direct Proof: read the exact logic for the dimension being asked
   - Infrastructure Hint: schema/hooks/types only, not the specific logic
   - Partial Implementation: some footprints present, others missing
   - Global Absence: searched ALL relevant invariants across ENTIRE repo,
     found no footprint

4. Cite without fabricating
   Quote 3-5 lines of actual matched code. Include path + line range IF
   the tool provided them. Never invent line numbers.

5. Conclusion leads with epistemic status:
   "For the [dimension], evidence = [Direct Proof / Infrastructure Hint /
   Partial Implementation / Global Absence]; matches in [files] show [what];
   structural footprint scan of [invariants] returned [result]."

Fallback (Safe Mode):
Answer is "let me check first", NOT "X isn't built", if any of:
- Unable to name the dimension precisely
- Footprint scan returned matches you haven't read
- Unsure which architectural pattern applies AND haven't searched alternatives
- The user pushed back on a similar claim recently

Self-check triggers:
- "I'd remember if we built this"
- "BACKLOG looks confident"
- "I just need to check one file"
- "My mental model of this system feels obvious" (← especially this one)

Honest limits:
- Wrong mental model of the architecture can still produce structurally
  rigorous wrong audits. The stack-discipline sub-step is a hedge, not a fix.
- Generated code, external services, dynamic dispatch, and indirection can
  evade footprint scans even when the feature exists.
- "Global" means global-within-visible-code, not global-within-system.
- Discipline is in the practice, not the prose. A 700-token rule
  half-followed is worse than a 200-token rule actually followed.
- This rule reduces but does not eliminate misclaims.
- When the architectural ontology is unclear, ask the user before concluding.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the rule. Now the reasoning.&lt;/p&gt;

&lt;p&gt;I had Claude Code as my coding agent on a personal automation project. By 11 AM one morning, the agent had confidently claimed "feature X is not built" four times in a row, each time wrong, each time caught only by my pushback. The pattern was identical: trust the project's BACKLOG framing, do a narrow grep, miss adjacent layers, declare absence.&lt;/p&gt;

&lt;p&gt;The standard hallucination story does not fit. The agent searched. It searched fine. It just searched for the wrong shapes.&lt;/p&gt;

&lt;p&gt;What I observed: the agent was searching by name when it should be searching by shape. A feature can be called anything. A feature cannot exist without leaving structural residue. There has to be a route, a schema, a registered tool, a scheduled job, a documented decision. When the agent searches by name, it is asking what string would this feature use (a question about vocabulary). When it searches by shape, it is asking what artifact would this feature require (a question about architecture).&lt;/p&gt;

&lt;p&gt;The rule above forces the second question.&lt;/p&gt;

&lt;p&gt;I ran the rule through eight critiques across four rounds before settling on this version. The biggest substantive shift was structural-footprint-vs-synonyms. The earlier draft had me generating better synonyms when stuck. That just relocates the dependency on the agent's imagination. The structural-footprint version asks a different question: what artifact would prove the feature exists? Then grep for that artifact. The dependency moves from imagination to architectural knowledge, which is more reliable.&lt;/p&gt;

&lt;p&gt;The other major addition was the absence-scope distinction: "I searched module X and found nothing" is a scope claim, not a fact claim. The fix is making absence claims global on the architectural invariant.&lt;/p&gt;

&lt;p&gt;The rule has known limits. They are listed in the rule itself. The biggest one is wrong-ontology rigor: an agent could generate a structurally rigorous footprint search against the wrong architectural pattern (e.g., search GraphQL patterns on a REST system) and confidently confirm absence. The stack-discipline sub-step is a hedge, not a fix.&lt;/p&gt;

&lt;p&gt;What I want from you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Try it.&lt;/strong&gt; Run it as a system rule on a project where you use an AI coding agent for a few sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell me what breaks.&lt;/strong&gt; Specifically: hallucination shapes the structural footprint search would NOT catch, audit-theater patterns where the form is satisfied without the substance, over-triggering on questions that were not actually absence claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell me what you have written.&lt;/strong&gt; If you have rules in your own CLAUDE.md or system prompt that solve adjacent problems, I want to read them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am running this on a separate project for two to three weeks before deciding whether to graduate it to my global agent configuration. After that I will know whether to keep it, refine it, or archive it. Your test reports compress that timeline.&lt;/p&gt;

&lt;p&gt;Reply or DM on whichever platform you found this.&lt;/p&gt;

&lt;p&gt;The misspelling stays.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>claude</category>
    </item>
    <item>
      <title>Pre-Build Existence Audit Rule : looking for the failure modes I'm still missing</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Wed, 06 May 2026 12:02:46 +0000</pubDate>
      <link>https://dev.to/natevoss/pre-build-existence-audit-rule-looking-for-the-failure-modes-im-still-missing-4kc5</link>
      <guid>https://dev.to/natevoss/pre-build-existence-audit-rule-looking-for-the-failure-modes-im-still-missing-4kc5</guid>
      <description>&lt;p&gt;I wrote a rule for AI coding agents two days ago. It is untested in production sessions. I am posting it here to find its failure modes faster than I would by waiting for my own future mistakes to surface them.&lt;/p&gt;

&lt;p&gt;The rule is below first. Story and reasoning after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pre-Build Existence Audit Rule (v1, structural verification)

Status: Untested in production sessions. Test on a new project for 2-3 weeks
before considering global rollout.

Before claiming "feature X is not built / not implemented / missing":

1. Map
   rg -li "&amp;lt;keyword&amp;gt;" .                            # project repo
   rg -li "&amp;lt;keyword&amp;gt;" ~/.claude/projects/*/memory/ # agent memory
   If either &amp;gt;5 files match, use the file list to scope which to read.

2. Structural footprint scan (NOT just synonyms)
   Identify architectural invariants this feature class would require:
   - Integration/API → router definitions, endpoint registrations,
     plugin tool lists
   - Data → schema files, migrations, type definitions, persisted-entity fields
   - Background → cron entries, queue handlers, scheduled job registrations
   - Cross-service → service registry, infra config, IPC handlers
   - Memory/decisions → project_*.md files documenting prior shipment

   Stack discipline: footprints must be stack-appropriate. If unsure which
   architectural pattern applies, list 2-3 plausible alternatives
   (REST/GraphQL/RPC; cron/queue/webhook) and search each. Wrong-ontology
   audits feel rigorous but miss truth.

   Grep each invariant. If ANY return matches, "not built" is contradicted
   until you've read those matches.

3. Epistemic categorization. Label each match as ONE of:
   - Direct Proof: read the exact logic for the dimension being asked
   - Infrastructure Hint: schema/hooks/types only, not the specific logic
   - Partial Implementation: some footprints present, others missing
   - Global Absence: searched ALL relevant invariants across ENTIRE repo,
     found no footprint

4. Cite without fabricating
   Quote 3-5 lines of actual matched code. Include path + line range IF
   the tool provided them. Never invent line numbers.

5. Conclusion leads with epistemic status:
   "For the [dimension], evidence = [Direct Proof / Infrastructure Hint /
   Partial Implementation / Global Absence]; matches in [files] show [what];
   structural footprint scan of [invariants] returned [result]."

Fallback (Safe Mode):
Answer is "let me check first", NOT "X isn't built", if any of:
- Unable to name the dimension precisely
- Footprint scan returned matches you haven't read
- Unsure which architectural pattern applies AND haven't searched alternatives
- The user pushed back on a similar claim recently

Self-check triggers:
- "I'd remember if we built this"
- "BACKLOG looks confident"
- "I just need to check one file"
- "My mental model of this system feels obvious" (← especially this one)

Honest limits:
- Wrong mental model of the architecture can still produce structurally
  rigorous wrong audits. The stack-discipline sub-step is a hedge, not a fix.
- Generated code, external services, dynamic dispatch, and indirection can
  evade footprint scans even when the feature exists.
- "Global" means global-within-visible-code, not global-within-system.
- Discipline is in the practice, not the prose. A 700-token rule
  half-followed is worse than a 200-token rule actually followed.
- This rule reduces but does not eliminate misclaims.
- When the architectural ontology is unclear, ask the user before concluding.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the rule. Now the reasoning.&lt;/p&gt;

&lt;p&gt;I had Claude Code as my coding agent on a personal automation project. By 11 AM one morning, the agent had confidently claimed "feature X is not built" four times in a row, each time wrong, each time caught only by my pushback. The pattern was identical: trust the project's BACKLOG framing, do a narrow grep, miss adjacent layers, declare absence.&lt;/p&gt;

&lt;p&gt;The standard hallucination story does not fit. The agent searched. It searched fine. It just searched for the wrong shapes.&lt;/p&gt;

&lt;p&gt;What I observed: the agent was searching by name when it should be searching by shape. A feature can be called anything. A feature cannot exist without leaving structural residue. There has to be a route, a schema, a registered tool, a scheduled job, a documented decision. When the agent searches by name, it is asking what string would this feature use (a question about vocabulary). When it searches by shape, it is asking what artifact would this feature require (a question about architecture).&lt;/p&gt;

&lt;p&gt;The rule above forces the second question.&lt;/p&gt;

&lt;p&gt;I ran the rule through eight critiques across four rounds before settling on this version. The biggest substantive shift was structural-footprint-vs-synonyms. The earlier draft had me generating better synonyms when stuck. That just relocates the dependency on the agent's imagination. The structural-footprint version asks a different question: what artifact would prove the feature exists? Then grep for that artifact. The dependency moves from imagination to architectural knowledge, which is more reliable.&lt;/p&gt;

&lt;p&gt;The other major addition was the absence-scope distinction: "I searched module X and found nothing" is a scope claim, not a fact claim. The fix is making absence claims global on the architectural invariant.&lt;/p&gt;

&lt;p&gt;The rule has known limits. They are listed in the rule itself. The biggest one is wrong-ontology rigor: an agent could generate a structurally rigorous footprint search against the wrong architectural pattern (e.g., search GraphQL patterns on a REST system) and confidently confirm absence. The stack-discipline sub-step is a hedge, not a fix.&lt;/p&gt;

&lt;p&gt;What I want from you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Try it.&lt;/strong&gt; Run it as a system rule on a project where you use an AI coding agent for a few sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell me what breaks.&lt;/strong&gt; Specifically: hallucination shapes the structural footprint search would NOT catch, audit-theater patterns where the form is satisfied without the substance, over-triggering on questions that were not actually absence claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tell me what you have written.&lt;/strong&gt; If you have rules in your own CLAUDE.md or system prompt that solve adjacent problems, I want to read them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am running this on a separate project for two to three weeks before deciding whether to graduate it to my global agent configuration. After that I will know whether to keep it, refine it, or archive it. Your test reports compress that timeline.&lt;/p&gt;

&lt;p&gt;Reply or DM on whichever platform you found this.&lt;/p&gt;

&lt;p&gt;The misspelling stays.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
      <category>claude</category>
    </item>
    <item>
      <title>4 rules I added to my CLAUDE.md after a week of weird CLI bugs</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Wed, 06 May 2026 10:45:50 +0000</pubDate>
      <link>https://dev.to/natevoss/4-rules-i-added-to-my-claudemd-after-a-week-of-weird-cli-bugs-p6c</link>
      <guid>https://dev.to/natevoss/4-rules-i-added-to-my-claudemd-after-a-week-of-weird-cli-bugs-p6c</guid>
      <description>&lt;p&gt;I shipped a small CLI tool last week and ran it for the first time on my own machine. The output had a number that read &lt;code&gt;1,28,000&lt;/code&gt; instead of &lt;code&gt;128,000&lt;/code&gt;. I stared at it for a minute, ran the same code in a node REPL, got the same wrong number back, and realized my locale was doing it. A few hours later I had four entries in my CLAUDE.md file that I should have written months ago.&lt;/p&gt;

&lt;p&gt;Here they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Always pass an explicit locale to &lt;code&gt;toLocaleString&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Bare &lt;code&gt;(128000).toLocaleString()&lt;/code&gt; is a trap. It uses whatever the system locale happens to be. On my machine that's en-IN, which renders &lt;code&gt;128000&lt;/code&gt; as &lt;code&gt;1,28,000&lt;/code&gt;. On a US-locale machine the same code returns &lt;code&gt;128,000&lt;/code&gt;. The bug only shows up where the locale is set, which means CI passes, your unit tests on a fresh container pass, and the wrong separators land in production output.&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="c1"&gt;// don't&lt;/span&gt;
&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// do&lt;/span&gt;
&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&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 rule I added: any time you generate number-formatting code for CLI or any user-facing text, pass an explicit locale. Never call it bare.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep CLI output for AI-editor tools under five lines
&lt;/h2&gt;

&lt;p&gt;I learned this one by watching myself ignore my own tool. I'd built a small command for inspecting build artifacts and ran it inside an AI assistant's terminal. The result was a 12-line printout with the answer near the bottom. The assistant collapsed it behind a &lt;code&gt;Ctrl+O to expand&lt;/code&gt; prompt, and I never expanded it. The agent reading the output never saw the result either.&lt;/p&gt;

&lt;p&gt;If a CLI is designed to run inside an AI assistant's bash, the design constraint is that assistant's read window, not your own terminal. Result plus summary in 4 to 5 lines max. Verbose mode behind a flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ build ok
3 packages, 412kb gzipped
fastest: core (180ms)
slowest: ui (910ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That fits. Anything more elaborate gets folded behind the expand prompt and effectively disappears from both you and the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;npx &amp;lt;package&amp;gt;&lt;/code&gt; fails inside the package's own monorepo
&lt;/h2&gt;

&lt;p&gt;I burned half an afternoon on this one. I was developing the CLI inside a pnpm workspace, ran &lt;code&gt;npx my-cli&lt;/code&gt; to smoke-test it, and got resolver errors. The package built fine. The bin field was correct. Outside the repo it ran clean. Inside the workspace, the resolver had different ideas about which version of which thing to use, because workspace context confuses it.&lt;/p&gt;

&lt;p&gt;The fix is not a fix, it's a docs entry. If a CLI lives in a monorepo, your README should say &lt;code&gt;install globally with npm install -g&lt;/code&gt; or run from outside the project directory. The rule I added to CLAUDE.md tells the assistant to never suggest &lt;code&gt;npx &amp;lt;pkgname&amp;gt;&lt;/code&gt; as a smoke test from inside the same repo that defines the package.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Shared-library fixes need version bumps on both sides
&lt;/h2&gt;

&lt;p&gt;I had a CLI that depends on a small library of mine as an external npm package, not a bundled module. I found a bug in the library, fixed it, ran the CLI's tests against the local working tree, everything passed, and shipped. The bug was still live.&lt;/p&gt;

&lt;p&gt;The CLI's &lt;code&gt;package.json&lt;/code&gt; was pinned to the previous library version. Fixing the library does nothing downstream until you publish a new version of the library AND bump the consumer's dependency to match. Local test runs lie because they resolve to your working tree, not the published artifact.&lt;/p&gt;

&lt;p&gt;The rule, copy-paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If a fix touches a shared library that the consumer depends on as
an external npm package (not vendored, not workspace-linked):
  1. Publish the library with a new version
  2. Bump the consumer's dep range
  3. Publish the consumer
Otherwise the fix doesn't ship.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added that to CLAUDE.md as a checklist the assistant walks through before claiming a bug is fixed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If you want to copy one of these into your own setup, here's the format that works in CLAUDE.md, cursor rules, or copilot instructions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When generating any number-formatting code for CLI output or user-facing text, always pass an explicit locale to &lt;code&gt;toLocaleString&lt;/code&gt; (typically &lt;code&gt;'en-US'&lt;/code&gt;). Never call it bare. System locale varies by region and produces wrong thousands separators (e.g. &lt;code&gt;1,28,000&lt;/code&gt; instead of &lt;code&gt;128,000&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add it once, save yourself the next version of this same day.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Model Routing: 3 Things I Learned Sending Tasks to the Cheapest Model That Actually Works</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Mon, 04 May 2026 06:54:30 +0000</pubDate>
      <link>https://dev.to/natevoss/model-routing-3-things-i-learned-sending-tasks-to-the-cheapest-model-that-actually-works-4e31</link>
      <guid>https://dev.to/natevoss/model-routing-3-things-i-learned-sending-tasks-to-the-cheapest-model-that-actually-works-4e31</guid>
      <description>&lt;p&gt;Everyone benchmarks models. Sonnet beats Haiku on reasoning. Opus beats Sonnet. Haiku is fastest. These things are all true.&lt;/p&gt;

&lt;p&gt;But benchmarking and deploying are different games. At scale, the difference between Haiku at $0.80/million tokens and Sonnet at $3/million tokens isn't academic. It's $400+ monthly on a mid-size application. The trap is paying for capability you don't actually need because you never measured what you do need.&lt;/p&gt;

&lt;p&gt;I built a router to answer one question: which tasks in my actual workflow could run on the cheapest model without failing? The answer surprised me. And I learned that the real value isn't the savings. It's the forcing function. You can't implement routing without auditing exactly where your complexity lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 Things I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Your Intuition About Task Complexity Is Backwards
&lt;/h3&gt;

&lt;p&gt;You think something needs Sonnet. Your gut says: "this requires reasoning, obviously expensive model."&lt;/p&gt;

&lt;p&gt;So I measured. Content classification? Haiku handles 95% of real requests. Writing summaries? 88%. Extracting structured data? 92%. The edge cases that needed Sonnet were smaller than I'd guessed. And they were always the same types of edge cases.&lt;/p&gt;

&lt;p&gt;Here's the pattern I found: obvious cases are &lt;strong&gt;really&lt;/strong&gt; obvious to Haiku. Spam detection, data validation, simple extractions. Haiku nails these. The failures cluster in a small, identifiable category: ambiguous cases where the human answer is ambiguous. That's when you need Sonnet's nuance.&lt;/p&gt;

&lt;p&gt;But you don't know your edge case percentage until you try. Guessing leaves money on the table.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. You Need Observability Before Routing Saves Anything
&lt;/h3&gt;

&lt;p&gt;The instinct is to build the router first. "Let's write logic that detects complex requests and routes to Sonnet."&lt;/p&gt;

&lt;p&gt;This is backward. You need to measure first. Log every task with both Haiku and Sonnet responses side-by-side. Compare them. Find the patterns.&lt;/p&gt;

&lt;p&gt;Real questions to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When did Haiku refuse a task that Sonnet handled?&lt;/li&gt;
&lt;li&gt;How often do their answers differ, and which one was right?&lt;/li&gt;
&lt;li&gt;Was Haiku just uncertain, or actually wrong?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This requires instrumenting your inference layer. It takes a week. But you can't optimize what you can't see. Most teams skip this and build routers on intuition, which is why their routers are fragile.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Routing Rules Should Be Dumb, Not Smart
&lt;/h3&gt;

&lt;p&gt;The temptation: build a classifier that predicts task complexity. Input length heuristics, keyword matching, embedding similarity. Something sophisticated.&lt;/p&gt;

&lt;p&gt;Don't. Use a simple rule: &lt;strong&gt;"If the model reports low confidence, escalate to Sonnet."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This separates the decision from the task. Haiku tells you when it's uncertain. That's a signal you can act on immediately, without needing to predict the future.&lt;/p&gt;

&lt;p&gt;The dumb rule wins because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It adapts as your tasks change (no retraining)&lt;/li&gt;
&lt;li&gt;It's testable (you can verify the confidence threshold)&lt;/li&gt;
&lt;li&gt;It fails safely (escalation costs more but works)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smart rule loses because routing logic becomes load-bearing infrastructure. Requires constant tuning. Breaks when your data distribution shifts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&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;Anthropic&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@anthropic-ai/sdk&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="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&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;classifyWithFallback&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="nx"&gt;confidenceThreshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// First pass: try Haiku (cheap, fast)&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;haikuResponse&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-3-5-haiku-20241022&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;max_tokens&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="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Classify this text as: safe, unsafe, or review-needed. Return JSON with {classification, confidence}.

Text: "&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="s2"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;haikuResult&lt;/span&gt; &lt;span class="o"&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;haikuResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="c1"&gt;// Log all Haiku decisions (even successes)&lt;/span&gt;
 &lt;span class="c1"&gt;// You're building a dataset of "when does Haiku work?"&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;text&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;slice&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;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;haiku&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;haikuResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;haikuResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;tokensUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;haikuResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
 &lt;span class="nx"&gt;haikuResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_tokens&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;

 &lt;span class="c1"&gt;// If Haiku is unsure, escalate to Sonnet&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;haikuResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;confidenceThreshold&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;sonnetResponse&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-3-5-sonnet-20241022&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;max_tokens&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="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Classify this text as: safe, unsafe, or review-needed. Return JSON with {classification, confidence}.

Text: "&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="s2"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sonnetResult&lt;/span&gt; &lt;span class="o"&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;sonnetResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;text&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;slice&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;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sonnet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;escalatedFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;haiku&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sonnetResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;tokensUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;sonnetResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
 &lt;span class="nx"&gt;sonnetResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_tokens&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;sonnetResult&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;haikuResult&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;That's it. Run both models in parallel during development and log the results. In production, start with Haiku, escalate on low confidence. As your logs accumulate, you'll see exactly which tasks need expensive models and which don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Haiku: $0.80 per 1M input tokens
Sonnet: $3 per 1M input tokens

Scenario: 1M requests/month, 200 tokens average
- All Sonnet: 1M × 200 tokens = $600
- 95% Haiku: (950k × 200) Haiku + (50k × 200) Sonnet = $152 + $30 = $182
- Savings: $418/month

At enterprise scale (100M requests/month): $41,800/month saved by routing to the cheapest viable model.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost difference compounds. Small routing decisions get multiplied across thousands of requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Common Pitfall
&lt;/h2&gt;

&lt;p&gt;You'll build a sophisticated router and wonder why it doesn't move the needle. Usually because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You spent three months on routing logic, but you spend one week validating it&lt;/li&gt;
&lt;li&gt;The escalation threshold is too aggressive ("if anything looks hard, use Sonnet")&lt;/li&gt;
&lt;li&gt;You're routing on heuristics, not observed behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix: measure first, always. Log both models' responses in parallel before committing to either one. You'll find that the obvious cases are really obvious, and the edge cases are smaller than you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Routing Actually Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have &amp;gt;100k requests/month (smaller volume doesn't justify overhead)&lt;/li&gt;
&lt;li&gt;Your requests fall into clusters (some are cheap tasks, some are hard)&lt;/li&gt;
&lt;li&gt;You can measure ground truth (compare Haiku vs Sonnet, track which was right)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don't build it if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;lt;10k requests/month (infrastructure overhead isn't worth it)&lt;/li&gt;
&lt;li&gt;Every request is unique and complex (no pattern to exploit)&lt;/li&gt;
&lt;li&gt;You need 99.9% accuracy (can't tolerate Haiku failures)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real Win
&lt;/h2&gt;

&lt;p&gt;The cost savings are real. But the bigger win is the audit itself. Building a router forces you to measure exactly where your complexity actually lives. Most teams overthink what they need because they never measure. The router is just the excuse to finally look.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>3 Things I Learned Auditing My LLM App's Token Spend (And Why Your Benchmarks Are Lying)</title>
      <dc:creator>Nate Voss</dc:creator>
      <pubDate>Mon, 27 Apr 2026 08:04:50 +0000</pubDate>
      <link>https://dev.to/natevoss/3-things-i-learned-auditing-my-llm-apps-token-spend-and-why-your-benchmarks-are-lying-3nbi</link>
      <guid>https://dev.to/natevoss/3-things-i-learned-auditing-my-llm-apps-token-spend-and-why-your-benchmarks-are-lying-3nbi</guid>
      <description>&lt;p&gt;You know that feeling when you ship an AI feature and realize your token bill is 3x what you estimated? Yeah, that was me last week.&lt;/p&gt;

&lt;p&gt;I have this thing called Agent-Max — it's a multi-platform growth agent that runs autonomous workflows: generating content, publishing to Bluesky, Medium, Twitter, Reddit. Sounds heavy, right? Every Monday it synthesizes a week of reading, scrapes engagement metrics, decides what to post and where. Seven platforms. Infinite LLM calls if you're not paying attention.&lt;/p&gt;

&lt;p&gt;Last Sunday I realized I had no idea what I was actually spending. I knew &lt;em&gt;roughly&lt;/em&gt; — "somewhere between $5-20/week" — but roughly is how you end up with bill shock. So I built PromptFuel to solve the actual problem: measure what your app is doing, not what the docs say it &lt;em&gt;should&lt;/em&gt; do.&lt;/p&gt;

&lt;p&gt;Here's what three days of auditing my own code taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your bottleneck isn't the model you picked, it's the prompt you didn't trim
&lt;/h2&gt;

&lt;p&gt;I assumed my biggest cost sink was the weekly reflection. Claude reads 7 days of snapshots, engagement data, content history, trend analysis, then reasons about next week's strategy. Heavy prompt, right?&lt;/p&gt;

&lt;p&gt;Nope.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;pf optimize&lt;/code&gt; on the actual prompts showed the reflection was 2,847 tokens. Not small, but fine. The real killer: the daily content pregeneration loop was calling Claude 5 times per platform, and each call had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entire engagement history (redundant. I'm fetching fresh data every run)&lt;/li&gt;
&lt;li&gt;Every. Single. Previous. Post. (all 120 of them, in the context)&lt;/li&gt;
&lt;li&gt;Current date, weather, trending topics (reloaded every call)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cutting history to "last 10 posts, last 3 days of engagement" knocked 40% off. Not because I switched models. Because I stopped hallucinating I needed context I wasn't even &lt;em&gt;reading&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Your audit will surface the dumb stuff, not the obvious stuff
&lt;/h2&gt;

&lt;p&gt;Benchmarks tell you Claude costs 3¢ per 1M input tokens. Haiku costs 0.8¢. Pick the right model, do the math, move on.&lt;/p&gt;

&lt;p&gt;Except I was calling Claude Sonnet 7 times/week on background analytics where Haiku was plenty. Not intentional. I'd copied the model from an earlier prompt and never thought about it again. One-line change, zero quality loss, $2 saved per month.&lt;/p&gt;

&lt;p&gt;That math &lt;em&gt;never&lt;/em&gt; shows up in a benchmark. It shows up in your actual codebase, on your actual data, running your actual job. PromptFuel's advantage isn't telling you models are expensive. It's finding the calls you forgot about and showing you the before/after side-by-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Once you see the numbers, the optimization loop becomes obvious
&lt;/h2&gt;

&lt;p&gt;The first time I ran the dashboard, I thought I was done. Then Monday's weekly job ran and I watched 47 new prompts execute. Dashboard updated in real time. I saw the pattern. There's another cut.&lt;/p&gt;

&lt;p&gt;Auditing once is useful. Auditing every week is how you stop bleeding money.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's walk through it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; promptfuel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run pf optimize&lt;/strong&gt; on a real prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pf optimize ./src/prompts/reflect.md &lt;span class="nt"&gt;--model&lt;/span&gt; claude-3-5-sonnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see token count, cost per call, and a readability score. More importantly, you'll see where the redundancy is hiding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open the dashboard&lt;/strong&gt; to watch prompts in real time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pf dashboard &lt;span class="nt"&gt;--watch&lt;/span&gt; ./src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 3000 opens. Every time you call an LLM, you see it log: model, input tokens, output tokens, cost, latency. No guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For production, wire up the SDK:&lt;/strong&gt;&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;PromptFuel&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;promptfuel/sdk&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="nx"&gt;Anthropic&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;@anthropic-ai/sdk&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="nx"&gt;pf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PromptFuel&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrapClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&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;response&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-3-5-sonnet-20241022&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your prompt&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;// Automatically tracked. One line changes nothing&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetrics&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; 
&lt;span class="c1"&gt;// { totalTokens: 342, totalCost: $0.008, calls: 1 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Real numbers
&lt;/h2&gt;

&lt;p&gt;Agent-Max before: ~1,847 tokens/week across all platforms.&lt;/p&gt;

&lt;p&gt;Agent-Max after (trimmed + downgraded safe calls to Haiku): 1,094 tokens/week.&lt;/p&gt;

&lt;p&gt;40% reduction. No quality loss. Three hours to audit and implement.&lt;/p&gt;

&lt;p&gt;That's not a benchmark. That's a real app, real prompts, real data.&lt;/p&gt;




&lt;p&gt;Stop guessing about your token spend. Measure what you're actually doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; promptfuel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://promptfuel.vercel.app?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=max" rel="noopener noreferrer"&gt;https://promptfuel.vercel.app?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=max&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
