<?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>Forem: Hiroshi Toyama</title>
    <description>The latest articles on Forem by Hiroshi Toyama (@toyama0919).</description>
    <link>https://forem.com/toyama0919</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%2F41804%2F7c54b968-6837-4276-9b8a-ac8f060c6f18.png</url>
      <title>Forem: Hiroshi Toyama</title>
      <link>https://forem.com/toyama0919</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/toyama0919"/>
    <language>en</language>
    <item>
      <title>Cursor vs Claude: The Business Models Behind the 10x Price Gap</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Thu, 07 May 2026 08:25:35 +0000</pubDate>
      <link>https://forem.com/toyama0919/cursor-vs-claude-the-business-models-behind-the-10x-price-gap-3lj7</link>
      <guid>https://forem.com/toyama0919/cursor-vs-claude-the-business-models-behind-the-10x-price-gap-3lj7</guid>
      <description>&lt;p&gt;The &lt;a href="https://dev.to/hiroshi/cursor-composer-2-the-cache-economy-behind-a-10x-cheaper-coding-agent-3600094"&gt;previous post&lt;/a&gt; covered Composer 2's cache mechanics and the Standard/Fast split. This one goes one level deeper: &lt;em&gt;why&lt;/em&gt; the price gap exists structurally, and what it predicts about where AI model markets are heading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Business Models
&lt;/h2&gt;

&lt;p&gt;The $0.50 vs $5.00 price gap between Composer 2 Standard and Claude Opus isn't primarily about model size. It's about two fundamentally different business models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic/OpenAI:&lt;/strong&gt; Build the most capable general-purpose model possible. License it as an API to anyone who wants to use it—enterprises, startups, individual developers. The general-purpose nature requires maintaining capabilities across every domain: legal reasoning, creative writing, mathematics, programming, ethics, philosophy. Margin on each API call covers model development, infrastructure, and business overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor/Anysphere:&lt;/strong&gt; Build a model &lt;em&gt;only&lt;/em&gt; for one product—Cursor. No external API to sell. No licensing fees to pay. No reason to maintain capabilities outside software development. The specialized training means stripping out everything that isn't code, resulting in a dramatically smaller model that's cheaper to serve.&lt;/p&gt;

&lt;p&gt;The math follows directly. Composer 2 is trained exclusively on coding data via continued pre-training and reinforcement learning. Claude Opus maintains the ability to pass bar exams, write poetry, explain quantum mechanics, and argue ethics. You're paying for all of that whether you use it or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cache Write Tax
&lt;/h2&gt;

&lt;p&gt;This business model difference shows up most concretely in cache write pricing.&lt;/p&gt;

&lt;p&gt;Claude's prompt caching has three cost components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache write&lt;/strong&gt;: 1.25× the base input price&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache read&lt;/strong&gt;: ~10% of the base input price&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normal input&lt;/strong&gt;: base price&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That cache write surcharge exists because Anthropic is taking on the cost and risk of maintaining cached data for an external customer. They don't know what you'll cache, how long it'll stay relevant, or whether you'll return in 5 minutes or 5 days. The 1.25× write rate is essentially an infrastructure risk premium embedded in the API pricing.&lt;/p&gt;

&lt;p&gt;Composer 2's actual usage data tells a completely different story:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input (w/ Cache Write)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input (w/o Cache Write)&lt;/td&gt;
&lt;td&gt;15,018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Read&lt;/td&gt;
&lt;td&gt;391,424&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Input (w/ Cache Write)&lt;/code&gt; column is zero across every single Composer 2 request. Anysphere runs Composer 2 on their own servers, optimized for exactly one workload: Cursor's codebase-heavy sessions. There's no external API infrastructure risk to price in. The cache write surcharge simply doesn't exist.&lt;/p&gt;

&lt;p&gt;For Claude Opus users on Cursor, the same column is non-zero. Even though Cursor proxies the request, it still hits Anthropic's API and incurs the write premium.&lt;/p&gt;

&lt;p&gt;The practical effect: on a new session with a large codebase, Claude Opus users pay an entry fee (cache write at 1.25× rate) that Composer 2 users never encounter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Luxury Engineering
&lt;/h2&gt;

&lt;p&gt;A useful framing emerges from analyzing actual usage patterns: &lt;strong&gt;Luxury Engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using Claude Opus for routine coding tasks is the AI equivalent of hiring a full professor to write unit tests. The professor is qualified—arguably overqualified. They could do it. But you're paying for decades of expertise in domains completely irrelevant to the task: literature, philosophy, ethics, history. That overhead is embedded in every token.&lt;/p&gt;

&lt;p&gt;Composer 2 is more like a developer who has done nothing but code their entire career. No breadth, extraordinary depth in the one domain that matters. Because of that specialization, cost is 1/10th.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Model Landscape
&lt;/h2&gt;

&lt;p&gt;Looking at the complete pricing picture (2026):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Cache Write&lt;/th&gt;
&lt;th&gt;Cache Read&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Composer 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.3 Codex&lt;/td&gt;
&lt;td&gt;$1.75&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.175&lt;/td&gt;
&lt;td&gt;$14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok 4.20&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.1 Pro&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 4.6 Sonnet&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$3.75 (1h: $6.00)&lt;/td&gt;
&lt;td&gt;$0.30&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.5&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 4.7 Opus&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$6.25 (1h: $10.00)&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GPT-5.3 Codex being significantly cheaper than GPT-5.5 follows the same logic: Codex uses continued pre-training on code data to reduce model weight, and the price difference is essentially "the cost of maintaining the ability to write poetry."&lt;/p&gt;

&lt;p&gt;Two patterns stand out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Claude cache write anomaly.&lt;/strong&gt; Only Claude models carry an explicit cache write surcharge. Every other model in this list (including Composer 2) absorbs the write cost into the base price or waives it entirely. This isn't a product limitation—it's a reflection of Claude's external API business model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composer 2's output price.&lt;/strong&gt; $2.50/1M output is 10× cheaper than Claude Opus and 12× cheaper than GPT-5.5. Code generation produces significant output token volume. Composer 2's extreme output pricing means that long agentic sessions—the exact workloads it's designed for—don't hit a cost ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code: The Name Is Misleading
&lt;/h2&gt;

&lt;p&gt;The name "Claude Code" implies a coding-specialized model. It isn't. Claude Code is Claude 4.6 Opus or Sonnet—the same general-purpose models available in Cursor—packaged as a CLI tool. The underlying architecture hasn't been pruned for code; it retains the full weight of a general-purpose frontier model.&lt;/p&gt;

&lt;p&gt;The cost implications are direct. Claude Code uses Anthropic's standard Prompt Caching, which means the cache write premium (1.25×) applies. The default cache TTL is 5 minutes—long enough to expire while you're running tests or reading docs between prompts. The &lt;code&gt;ENABLE_PROMPT_CACHING_1H=1&lt;/code&gt; flag extends it to one hour, but doubles the write cost in exchange.&lt;/p&gt;

&lt;p&gt;The "autonomous loop" (run tests → read failure → fix code → rerun) is frequently cited as a Claude Code advantage. It isn't unique to Claude Code. Cursor's agent mode executes the same loop via its sandboxed terminal integration. The practical difference is that Cursor's loop doesn't incur a cache write penalty on session start, and runs cache reads at $0.20/1M rather than Claude's ~$0.50/1M.&lt;/p&gt;

&lt;p&gt;Where Claude Code has a genuine edge: terminal-native workflows for developers using Vim, JetBrains, or any editor outside the Cursor ecosystem. If you're not using Cursor, Claude Code is the most capable CLI agent available. Within Cursor, the economic case for Claude Code over Composer 2 is thin for standard coding tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for the Future
&lt;/h2&gt;

&lt;p&gt;This structure predicts where AI model markets go.&lt;/p&gt;

&lt;p&gt;General-purpose frontier models have a structural cost floor. They have to maintain broad capabilities to justify API pricing across diverse customers. They have to earn margins on external licensing. They have to maintain the "impressive demo" factor that drives enterprise adoption.&lt;/p&gt;

&lt;p&gt;Specialized models built for a specific product have none of those constraints. Strip capability, reduce model size, optimize serving infrastructure, eliminate external API margins. The only question is whether sufficient domain quality can be achieved.&lt;/p&gt;

&lt;p&gt;Composer 2 answered that question for software development in March 2026. SWE-bench Multilingual score of 73.7, at 1/10th the cost of Claude Opus.&lt;/p&gt;

&lt;p&gt;The same economics will play out in other domains: legal AI products trained exclusively on case law and contracts; medical AI running on clinical literature with zero consumer chat capability; financial models stripped of everything except numerical reasoning and accounting standards. None of them need to know how to write a sonnet.&lt;/p&gt;

&lt;p&gt;The structural enabler in each case is the same: building a model for &lt;em&gt;one product&lt;/em&gt;, not for external licensing. That eliminates the margin layer and enables the infrastructure optimizations that make 5-10× price reduction possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rational Selection Framework
&lt;/h2&gt;

&lt;p&gt;Given this analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Composer 2 Standard&lt;/strong&gt; for any multi-turn session against a codebase. Cache compound interest works in your favor: higher turn count → higher cache read ratio → lower effective cost per token. No cache write entry fee on session start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composer 2 Fast&lt;/strong&gt; for interactive sessions where latency matters more than per-token cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Opus or Claude 4.7&lt;/strong&gt; when you genuinely need cross-domain reasoning—architecture decisions involving organizational and technical trade-offs simultaneously, debugging scenarios requiring external systems understanding outside your loaded context, or when Composer 2 hits an explicit capability ceiling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From actual usage data: 88.3% cache read ratio on Composer 2 Standard, $0.19 average cost per request on ~390K token requests. The same request volume on Claude Opus: $0.90 average. The top Opus request cost $4.25—enough for 22 equivalent Composer 2 Standard sessions.&lt;/p&gt;

&lt;p&gt;The price gap isn't a temporary marketing discount. It's structural, rooted in business model differences that won't close without a fundamental change in how Anthropic operates. As long as Claude is an external API product, the cache write premium and the overhead of general-purpose training remain embedded in the price.&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>ai</category>
      <category>productivity</category>
      <category>codingtools</category>
    </item>
    <item>
      <title>Using llms.txt with Cursor and Claude Code: a concrete playbook</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sun, 03 May 2026 11:56:30 +0000</pubDate>
      <link>https://forem.com/toyama0919/using-llmstxt-with-cursor-and-claude-code-a-concrete-playbook-4jln</link>
      <guid>https://forem.com/toyama0919/using-llmstxt-with-cursor-and-claude-code-a-concrete-playbook-4jln</guid>
      <description>&lt;p&gt;&lt;strong&gt;llms.txt&lt;/strong&gt; is a small text file on a documentation site—usually lists what the product is and links to the important Markdown pages. For coding agents, treat it as &lt;strong&gt;the canonical URL to open first&lt;/strong&gt; when upstream behavior is unclear. This post is mostly &lt;strong&gt;setup and workflow&lt;/strong&gt;, not theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes where
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Put this there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Official doc server&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;https://example.com/llms.txt&lt;/code&gt; (maintained by the library/vendor)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your repo&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;URLs only&lt;/strong&gt; (and short protocols), in agent rules—not a copy of their docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your repo &lt;code&gt;.cursor/rules/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Project map, conventions, &lt;em&gt;your&lt;/em&gt; architecture—not Next.js’s full manual&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you paste thousands of tokens of upstream docs into rules, every chat pays for them. Keeping &lt;strong&gt;pointers&lt;/strong&gt; in rules and loading docs &lt;strong&gt;on demand&lt;/strong&gt; avoids that.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-time setup: a dedicated rules file
&lt;/h2&gt;

&lt;p&gt;Create something like &lt;code&gt;.cursor/rules/external-llms-docs.md&lt;/code&gt; (name does not matter; keep it scoped). Paste a &lt;strong&gt;stable list&lt;/strong&gt; of llms.txt URLs your stack actually uses, grouped so humans and agents scan quickly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# External docs — fetch on demand&lt;/span&gt;

Use web fetch / browser / search tools to load these when implementing or debugging
third-party behavior. Do not paste full upstream docs into the chat.

&lt;span class="gu"&gt;## Index URLs (read these first)&lt;/span&gt;

| Area | llms.txt |
| --- | --- |
| Next.js | https://nextjs.org/llms.txt |
| Tailwind | https://tailwindcss.com/llms.txt |
| Lucide | https://lucide.dev/llms.txt |
| Google ADK | https://adk.dev/llms.txt |

&lt;span class="gu"&gt;## Read order&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Fetch the &lt;span class="gs"&gt;**llms.txt**&lt;/span&gt; for the dependency that owns the question.
&lt;span class="p"&gt;2.&lt;/span&gt; Follow &lt;span class="gs"&gt;**only**&lt;/span&gt; links from that file (or obvious &lt;span class="sb"&gt;`/docs/*.md`&lt;/span&gt; siblings) for depth.
&lt;span class="p"&gt;3.&lt;/span&gt; Prefer Markdown sources over scraping marketing HTML.
&lt;span class="p"&gt;4.&lt;/span&gt; If types exist locally (&lt;span class="sb"&gt;`node_modules`&lt;/span&gt;, stubs), use them &lt;span class="gs"&gt;**after**&lt;/span&gt; you know which API surface applies (avoids guessing wrong symbols).

&lt;span class="gu"&gt;## Scope&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Questions about &lt;span class="gs"&gt;**our**&lt;/span&gt; repo layout → use &lt;span class="sb"&gt;`repo-map`&lt;/span&gt; rule / codebase search, not llms.txt.
&lt;span class="p"&gt;-&lt;/span&gt; Questions about &lt;span class="gs"&gt;**their**&lt;/span&gt; API/version/docs → use the table above.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why a separate file: Cursor injects rules by context; a fat global rule file makes unrelated edits heavier. Split &lt;strong&gt;internal&lt;/strong&gt; vs &lt;strong&gt;external&lt;/strong&gt; pointers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent protocol (copy into the same file or AGENTS.md)
&lt;/h2&gt;

&lt;p&gt;Make the sequence explicit so the model does not default to “grep &lt;code&gt;node_modules&lt;/code&gt; for an hour.”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## External SDK protocol&lt;/span&gt;

When the user asks for behavior that depends on an external library version or API:
&lt;span class="p"&gt;
1.&lt;/span&gt; Identify which dependency owns the feature (package.json / imports).
&lt;span class="p"&gt;2.&lt;/span&gt; If this file lists an llms.txt for that dependency, &lt;span class="gs"&gt;**fetch it before**&lt;/span&gt; writing code.
&lt;span class="p"&gt;3.&lt;/span&gt; Summarize in ≤10 lines: version assumptions, file names, and APIs you will use—then implement.
&lt;span class="p"&gt;4.&lt;/span&gt; Do not quote entire upstream pages back to the user; cite chapter/section or URL path only.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Concrete workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Implement a feature (e.g. App Router auth middleware).&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User: “Add middleware-based auth with Next.js App Router.”&lt;/li&gt;
&lt;li&gt;Agent: fetch &lt;code&gt;https://nextjs.org/llms.txt&lt;/code&gt;, open the linked page that describes &lt;code&gt;middleware.ts&lt;/code&gt; / matcher patterns.&lt;/li&gt;
&lt;li&gt;Implement using &lt;strong&gt;current&lt;/strong&gt; filenames and signatures from that fetch—not memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Debug “works on my machine” / deprecation.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User: “Tailwind v4 class names stopped working after upgrade.”&lt;/li&gt;
&lt;li&gt;Agent: fetch Tailwind’s llms.txt first; confirm breaking-change notes and config file names, then open repo &lt;code&gt;tailwind.config.*&lt;/code&gt; / CSS entry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;SDK with tiered dumps (example pattern).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some sites expose a short index and a long bundle (names vary). Rule of thumb: &lt;strong&gt;start short&lt;/strong&gt;, upgrade to full only if the stub did not answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# hypothetical layout on a docs host
/llms.txt          → links + overview
/llms-small.txt    → minimal surface (cheap)
/llms-full.txt     → everything (expensive)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your rules at the &lt;strong&gt;entry&lt;/strong&gt; (&lt;code&gt;llms.txt&lt;/code&gt;); let the fetched content tell the agent whether &lt;code&gt;*-full&lt;/code&gt; exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompts that reinforce good habits
&lt;/h2&gt;

&lt;p&gt;You can nudge behavior per task without editing rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Before editing: fetch Next.js llms.txt and confirm middleware filename and export shape.”&lt;/li&gt;
&lt;li&gt;“Use ADK llms.txt; don’t rely on training cutoff for API names.”&lt;/li&gt;
&lt;li&gt;“After fetching Tailwind llms.txt, list which doc URLs you used (paths only).”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Minimal internal llms.txt (optional)
&lt;/h2&gt;

&lt;p&gt;If &lt;strong&gt;you&lt;/strong&gt; ship an internal library or architecture handbook on HTTPS, you can publish your own index at &lt;code&gt;https://internal-docs.example.com/llms.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Internal platform — LLM index&lt;/span&gt;

&lt;span class="gu"&gt;## Auth&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Overview: https://internal-docs.example.com/auth/overview.md
&lt;span class="p"&gt;-&lt;/span&gt; Breaking changes 2026: https://internal-docs.example.com/auth/changelog.md

&lt;span class="gu"&gt;## Data layer&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; API conventions: https://internal-docs.example.com/db/conventions.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add one line to &lt;code&gt;.cursor/rules/external-llms-docs.md&lt;/code&gt;: &lt;code&gt;Internal platform | https://internal-docs.example.com/llms.txt&lt;/code&gt;. Same mechanics as vendor docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling reality check
&lt;/h2&gt;

&lt;p&gt;This pattern assumes the agent can &lt;strong&gt;retrieve HTTPS text&lt;/strong&gt; (built-in fetch, browser tool, MCP &lt;code&gt;fetch&lt;/code&gt;, etc.). Air-gapped machines need a fallback (mirror snippets in rules, local static server, or vendor tarball—but accept resident token cost).&lt;/p&gt;

&lt;p&gt;Do not put &lt;strong&gt;authenticated&lt;/strong&gt; URLs with secrets in rules; use public docs or internal SSO-aware tooling outside plain markdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dumping full upstream Markdown into &lt;code&gt;.cursorrules&lt;/code&gt; “so the agent always knows.”&lt;/li&gt;
&lt;li&gt;Skipping llms.txt and crawling random marketing pages (noisy HTML, wasted tokens).&lt;/li&gt;
&lt;li&gt;Duplicating vendor docs under &lt;code&gt;docs/vendor/&lt;/code&gt; and indexing everything unless you truly need offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SEO note (short)
&lt;/h2&gt;

&lt;p&gt;Search-engine teams have questioned llms.txt as an SEO lever; that is largely orthogonal. &lt;strong&gt;For coding agents&lt;/strong&gt;, the win is predictable Markdown entrypoints and smaller always-on context—not rankings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;.cursor/rules/external-llms-docs.md&lt;/code&gt; with a &lt;strong&gt;table of llms.txt URLs&lt;/strong&gt; plus &lt;strong&gt;read order&lt;/strong&gt; and &lt;strong&gt;scope&lt;/strong&gt; (external vs internal repo map).&lt;/li&gt;
&lt;li&gt;Teach agents: &lt;strong&gt;fetch index → follow linked Markdown → then local types&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Use tiered files &lt;strong&gt;shallow-first&lt;/strong&gt; when the provider offers them.&lt;/li&gt;
&lt;li&gt;Optionally host &lt;strong&gt;your own&lt;/strong&gt; llms.txt for internal platforms; still keep rules as pointers only.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>cursor</category>
      <category>llm</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Cursor Composer 2: The Cache Economy Behind a 10x Cheaper Coding Agent</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sat, 02 May 2026 12:53:01 +0000</pubDate>
      <link>https://forem.com/toyama0919/cursor-composer-2-the-cache-economy-behind-a-10x-cheaper-coding-agent-15cj</link>
      <guid>https://forem.com/toyama0919/cursor-composer-2-the-cache-economy-behind-a-10x-cheaper-coding-agent-15cj</guid>
      <description>&lt;p&gt;Cursor's Composer 2 shipped in March 2026 as the centerpiece of the Cursor 2.0 overhaul. The headline numbers—$0.50/1M input tokens, outperforming frontier models on SWE-bench Multilingual—look like marketing. The cache read mechanism is where the real story is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Specialized Model at All
&lt;/h2&gt;

&lt;p&gt;Prior Cursor versions proxied Claude or GPT-4. Composer 2 is trained exclusively on coding data via continued pre-training and reinforcement learning. The obvious question is: what's cut?&lt;/p&gt;

&lt;p&gt;Everything that isn't code. Composer 2 has no meaningful capability for poetry, history, ethics debates, or anything outside software development. That constraint lets Anysphere run a model that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understands intra-repo dependency graphs (if you fix A, B also needs updating)&lt;/li&gt;
&lt;li&gt;Navigates hundreds of files in a single long-horizon task&lt;/li&gt;
&lt;li&gt;Runs natively in sandboxed terminals and a built-in browser loop&lt;/li&gt;
&lt;li&gt;Costs a fraction of what a general-purpose frontier model costs to serve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pricing reflects this. As of May 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input (1M tokens)&lt;/th&gt;
&lt;th&gt;Output (1M tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Composer 2 Standard&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composer 2 Fast&lt;/td&gt;
&lt;td&gt;$1.50&lt;/td&gt;
&lt;td&gt;$7.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 4.6 Opus&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;$25.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.4&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$15.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Standard vs Fast: Same Weights, Different Queue
&lt;/h2&gt;

&lt;p&gt;Anysphere's own language is unambiguous: "Same intelligence." The two variants share identical model weights and parameters. Fast gets priority queue on high-end GPUs (H800/B200 class); Standard runs on lower-priority compute with higher latency tolerance.&lt;/p&gt;

&lt;p&gt;This is a deliberate architectural choice. Inference cost scales with compute priority, not model capability. If you can tolerate a 10–30 second response delay, you get the same output for 1/3 the price.&lt;/p&gt;

&lt;p&gt;The practical split that Cursor power users have settled on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive sessions (Fast):&lt;/strong&gt; You're watching the output in real time. Latency kills flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fire-and-forget tasks (Standard):&lt;/strong&gt; Refactor 100 test files, generate JSDoc across the repo, migrate an entire API surface. Start it, close the laptop, come back to results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Cache Read Economy
&lt;/h2&gt;

&lt;p&gt;This is the mechanism that makes Standard compelling for large codebases.&lt;/p&gt;

&lt;p&gt;Every request to Composer 2 sends context: directory structure, recently opened files, conversation history. On the second, fifth, tenth turn of the same session, the majority of that context is identical to what was already sent. That's the cache.&lt;/p&gt;

&lt;p&gt;Cache read rates as of May 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;New input&lt;/th&gt;
&lt;th&gt;Cache read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;$0.50/1M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.20/1M&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;$1.50/1M&lt;/td&gt;
&lt;td&gt;$0.35/1M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By turn 5 of a non-trivial session, 80%+ of your input tokens are cache reads, not fresh input. Standard's cache read rate ($0.20) is 43% cheaper than Fast's ($0.35), and 60% cheaper than Standard's own new input rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concrete impact:&lt;/strong&gt; A refactoring session with 10 back-and-forth turns on a large codebase might consume 10M tokens. With Standard and healthy cache hits, that lands around $1.50–$2.00. The same session on Fast: $4.00–$5.00. On Claude 4.6 Opus: potentially $20+.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cache Bug (March–April 2026)
&lt;/h2&gt;

&lt;p&gt;The cache story has a footnote worth documenting.&lt;/p&gt;

&lt;p&gt;From late March through early April 2026, a backend bug caused Composer 2 Standard to emit cache read counts of zero—every request treated as fresh input at $0.50/1M even when the context was identical to the previous turn. Users reported credit burn rates 10x higher than expected. The irony: switching to Fast (which costs 3x more per token) actually resulted in lower total cost because cache was functioning there.&lt;/p&gt;

&lt;p&gt;Cursor's team (Dean and Mohit on the forum thread) acknowledged the bug and pushed a fix around April 7. As of v2.1.116+, the behavior appears stable.&lt;/p&gt;

&lt;p&gt;The diagnostic check: open &lt;code&gt;cursor.com/settings&lt;/code&gt; → Usage. If &lt;code&gt;Cache Read&lt;/code&gt; tokens are consistently below 40% on a multi-turn session against the same codebase, something is wrong. Expected range is 40–90% depending on how varied your requests are.&lt;/p&gt;

&lt;p&gt;If you hit zero cache read consistently, copy the Request ID from the chat header and contact support. Cursor has been issuing credit refunds for the overbilling period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing with Claude Code's Cache
&lt;/h2&gt;

&lt;p&gt;Claude Code (Anthropic's CLI tool) has its own prompt caching via &lt;code&gt;cache_control&lt;/code&gt; markers, but with a key structural difference: TTL.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Write cost&lt;/th&gt;
&lt;th&gt;Read cost&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;1.25× input&lt;/td&gt;
&lt;td&gt;~10% of input&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ENABLE_PROMPT_CACHING_1H=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.0× input&lt;/td&gt;
&lt;td&gt;~10% of input&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 5-minute default is brutal for any session where you read documentation, test code, or think between turns. The 1-hour option (available since Claude Code v2.1.108) adds to the write cost but eliminates repeated cache misses across the kind of natural pauses that happen in real work.&lt;/p&gt;

&lt;p&gt;To enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc or ~/.bashrc&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ENABLE_PROMPT_CACHING_1H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with &lt;code&gt;usage&lt;/code&gt; output during a session—look for &lt;code&gt;ephemeral_1h_input_tokens&lt;/code&gt; in the log. If you only see &lt;code&gt;ephemeral_5m_&lt;/code&gt;, the variable isn't being picked up.&lt;/p&gt;

&lt;p&gt;Note: there were also TTL-related bugs in this period that forced resets to 5-minute behavior. Keep Claude Code at the latest version.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Usage Data
&lt;/h2&gt;

&lt;p&gt;I exported my own Cursor usage history and analyzed it. Here's what a month looks like across models (442 requests):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;th&gt;Avg cost/request&lt;/th&gt;
&lt;th&gt;Cache read ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Composer 2 Standard&lt;/td&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;$0.19&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;88.3%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composer 2 Fast&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;$0.32&lt;/td&gt;
&lt;td&gt;78.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 4.6 Sonnet&lt;/td&gt;
&lt;td&gt;212&lt;/td&gt;
&lt;td&gt;$0.37&lt;/td&gt;
&lt;td&gt;84.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 4.6 Opus&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;$0.90&lt;/td&gt;
&lt;td&gt;79.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 88.3% cache read ratio on Standard is the headline. For an average request consuming ~390K tokens, 88% of those are cache reads at $0.20/1M rather than fresh input at $0.50/1M. Without that cache hit rate, the average cost per request would be ~$0.40 instead of $0.19.&lt;/p&gt;

&lt;p&gt;The top Opus requests peaked at $4.25/request (3.9M total tokens, 3.8M of which were cache reads). Even with excellent cache ratios, Opus's higher base rates mean the same cache-heavy session costs 4–5× more than Composer 2 Standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Decision
&lt;/h2&gt;

&lt;p&gt;Composer 2 is not "Claude but cheap." It's a purpose-built agent runtime that has traded general intelligence for deep coding capability and cost efficiency at the infrastructure level. The Standard/Fast split exists because long-horizon agentic tasks don't need millisecond response times—and charging for that latency premium on 10-turn refactoring sessions is wasteful.&lt;/p&gt;

&lt;p&gt;The model choice that makes sense given this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default to Standard&lt;/strong&gt; for any multi-file task where you'll have more than 3–4 turns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Switch to Fast&lt;/strong&gt; for interactive chat where you're watching output incrementally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use frontier models (Opus, Claude 4.7)&lt;/strong&gt; only when Composer 2 hits a genuine capability ceiling—complex algorithmic reasoning, architecture decisions that span non-code domains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cache makes Standard not just "slower Fast," but a qualitatively different operational mode: background processing with cost amortized over a long context window that grows cheaper the more you reuse it.&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>ai</category>
      <category>productivity</category>
      <category>codingtools</category>
    </item>
    <item>
      <title>Two Nasty Gotchas When Building Multi-Agent Systems with Google ADK</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Tue, 28 Apr 2026 09:30:43 +0000</pubDate>
      <link>https://forem.com/toyama0919/two-nasty-gotchas-when-building-multi-agent-systems-with-google-adk-3d05</link>
      <guid>https://forem.com/toyama0919/two-nasty-gotchas-when-building-multi-agent-systems-with-google-adk-3d05</guid>
      <description>&lt;p&gt;Google's Agent Development Kit (ADK) makes it straightforward to compose &lt;code&gt;LlmAgent&lt;/code&gt; instances into multi-agent hierarchies. But two bugs bit me hard in production that aren't documented anywhere. Here's what happened and how to fix them.&lt;/p&gt;

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

&lt;p&gt;A root router &lt;code&gt;LlmAgent&lt;/code&gt; with two sub-agents. Both sub-agents are module-level singletons — instantiated at import time, referenced from the root agent's constructor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Agents/my_app/root_agent.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Agents.my_app.sub_agent_a.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sub_agent_a&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Agents.my_app.sub_agent_b.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sub_agent_b&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_build_sub_agents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sub_agent_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub_agent_b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_build_sub_agents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worked fine locally with &lt;code&gt;adk web&lt;/code&gt;. Blew up on Cloud Run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 1: &lt;code&gt;Agent already has a parent agent&lt;/code&gt; on module reload
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pydantic_core._pydantic_core.ValidationError: 1 validation error for LlmAgent
  Value error, Agent `SubAgentA` already has a parent agent,
  current parent: `my_app`, trying to add: `my_app`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's happening
&lt;/h3&gt;

&lt;p&gt;ADK's &lt;code&gt;agent_loader&lt;/code&gt; calls &lt;code&gt;importlib.import_module(agent_name)&lt;/code&gt; on &lt;strong&gt;every request&lt;/strong&gt;. On the first request, it loads the module fresh and creates &lt;code&gt;root_agent&lt;/code&gt;. The &lt;code&gt;LlmAgent&lt;/code&gt; constructor sets &lt;code&gt;sub_agent.parent_agent = root_agent&lt;/code&gt; for each sub-agent.&lt;/p&gt;

&lt;p&gt;On the second request, &lt;code&gt;agent_loader&lt;/code&gt; reloads the module. Because &lt;code&gt;sub_agent_a&lt;/code&gt; and &lt;code&gt;sub_agent_b&lt;/code&gt; are module-level singletons, &lt;strong&gt;they're the same Python objects&lt;/strong&gt; from the previous load — still carrying their &lt;code&gt;parent_agent&lt;/code&gt; reference. When the new &lt;code&gt;LlmAgent&lt;/code&gt; tries to assign the parent again, pydantic's validator rejects it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Inside ADK's LlmAgent.__init__ (simplified)
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_agent&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;` already has a parent agent ...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This never surfaces locally because &lt;code&gt;adk web&lt;/code&gt; loads the module only once per session. Cloud Run's request-per-reload behavior is what triggers it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Reset &lt;code&gt;parent_agent&lt;/code&gt; to &lt;code&gt;None&lt;/code&gt; before passing sub-agents to the constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_build_sub_agents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sub_agent_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub_agent_b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# reset before each reload
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safe because the assignment happens synchronously before the new parent is set.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 2: &lt;code&gt;Context variable not found&lt;/code&gt; in instruction strings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KeyError: 'Context variable not found: `hostname`.'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traceback points here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.../google/adk/utils/instructions_utils.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inject_session_state&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_async_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{+[^{}]*}+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_replace_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's happening
&lt;/h3&gt;

&lt;p&gt;ADK injects session state into agent instructions at runtime. The mechanism scans the instruction string with the regex &lt;code&gt;r'{+[^{}]*}+'&lt;/code&gt; and replaces every &lt;code&gt;{var_name}&lt;/code&gt; with the corresponding session state value.&lt;/p&gt;

&lt;p&gt;If your instruction contains an example URL or any template-like text with curly braces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The URL format is &lt;span class="sb"&gt;`https://{hostname}/api/{resource_id}/`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ADK sees &lt;code&gt;{hostname}&lt;/code&gt;, looks it up in session state, finds nothing, raises &lt;code&gt;KeyError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My first instinct was to double-brace escape like Python's &lt;code&gt;.format()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;https://{{hostname}}/api/{{resource_id}}/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This does not work.&lt;/strong&gt; The regex is &lt;code&gt;{+[^{}]*}+&lt;/code&gt; — it matches one or more &lt;code&gt;{&lt;/code&gt; characters followed by non-brace characters followed by one or more &lt;code&gt;}&lt;/code&gt; characters. &lt;code&gt;{{hostname}}&lt;/code&gt; still matches.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Don't use curly braces for literal placeholder text in instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The URL format is &lt;span class="sb"&gt;`https://&amp;lt;hostname&amp;gt;/api/&amp;lt;resource_id&amp;gt;/`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More broadly: &lt;strong&gt;any &lt;code&gt;{word}&lt;/code&gt; pattern in an ADK instruction string is treated as a session state variable&lt;/strong&gt;, regardless of how many braces you use. Use angle brackets, square brackets, or prose for template-like text in prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;parent_agent&lt;/code&gt; collision&lt;/td&gt;
&lt;td&gt;Module-level singleton sub-agents + ADK module reload per request&lt;/td&gt;
&lt;td&gt;Reset &lt;code&gt;agent.parent_agent = None&lt;/code&gt; before passing to constructor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Context variable not found&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{word}&lt;/code&gt; patterns in instruction strings&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;&amp;lt;word&amp;gt;&lt;/code&gt; or square brackets instead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both are easy to fix once you know what's happening, but the error messages don't immediately point to the root cause. The &lt;code&gt;parent_agent&lt;/code&gt; one is especially sneaky — it only appears in production where the module is reloaded per request, never in &lt;code&gt;adk web&lt;/code&gt; during local development.&lt;/p&gt;

</description>
      <category>googleadk</category>
      <category>llm</category>
      <category>python</category>
      <category>multiagent</category>
    </item>
    <item>
      <title>Managing AI Agent Skills with `npx skills`: A Practical Guide</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sat, 11 Apr 2026 08:04:45 +0000</pubDate>
      <link>https://forem.com/toyama0919/managing-ai-agent-skills-with-npx-skills-a-practical-guide-2an8</link>
      <guid>https://forem.com/toyama0919/managing-ai-agent-skills-with-npx-skills-a-practical-guide-2an8</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;AI agents like Claude Code, Cursor, and GitHub Copilot don't inherently know how to use every tool in your stack. You need a way to teach them. That's what &lt;code&gt;npx skills&lt;/code&gt; does — it's a package manager for AI agent behaviors, built by Vercel Labs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add microsoft/playwright-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command fetches a &lt;code&gt;SKILL.md&lt;/code&gt; from the specified GitHub repository and installs it into your agent's config directory (&lt;code&gt;.agents/skills/&lt;/code&gt; or &lt;code&gt;.claude/skills/&lt;/code&gt; depending on the agent).&lt;/p&gt;

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

&lt;h3&gt;
  
  
  GitHub as the Registry
&lt;/h3&gt;

&lt;p&gt;Unlike npm which uses npmjs.com, &lt;code&gt;skills&lt;/code&gt; uses GitHub as its registry. The &lt;code&gt;microsoft/playwright-cli&lt;/code&gt; argument maps directly to &lt;code&gt;https://github.com/microsoft/playwright-cli&lt;/code&gt;. Any public GitHub repo with a &lt;code&gt;SKILL.md&lt;/code&gt; at root is a valid skill source.&lt;/p&gt;

&lt;p&gt;You can also install by full URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add https://github.com/microsoft/playwright-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SKILL.md as the Package Entry Point
&lt;/h3&gt;

&lt;p&gt;Each skill repo contains a &lt;code&gt;SKILL.md&lt;/code&gt; — the equivalent of &lt;code&gt;index.js&lt;/code&gt; in an npm package. It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata&lt;/strong&gt;: name and description of the skill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool definitions&lt;/strong&gt;: commands the AI can invoke (e.g. &lt;code&gt;playwright test&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt instructions&lt;/strong&gt;: when and how the AI should use the tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  .skills.json + skills-lock.json = package.json + package-lock.json
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;npm&lt;/th&gt;
&lt;th&gt;skills CLI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependency manifest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.skills.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;package-lock.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;skills-lock.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install directory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node_modules/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registry&lt;/td&gt;
&lt;td&gt;npmjs.com&lt;/td&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install command&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx skills experimental_install&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After &lt;code&gt;npx skills add&lt;/code&gt;, your &lt;code&gt;.skills.json&lt;/code&gt; will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"playwright-cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"microsoft/playwright-cli"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a skill&lt;/span&gt;
npx skills add vercel-labs/agent-skills

&lt;span class="c"&gt;# Add globally (user-level, not project-level)&lt;/span&gt;
npx skills add vercel-labs/agent-skills &lt;span class="nt"&gt;-g&lt;/span&gt;

&lt;span class="c"&gt;# Target specific agents&lt;/span&gt;
npx skills add vercel-labs/agent-skills &lt;span class="nt"&gt;--agent&lt;/span&gt; claude-code cursor

&lt;span class="c"&gt;# List installed skills&lt;/span&gt;
npx skills list
npx skills &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;           &lt;span class="c"&gt;# global skills&lt;/span&gt;
npx skills &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; cursor    &lt;span class="c"&gt;# filter by agent&lt;/span&gt;

&lt;span class="c"&gt;# Search the registry&lt;/span&gt;
npx skills find typescript

&lt;span class="c"&gt;# Update all skills&lt;/span&gt;
npx skills update

&lt;span class="c"&gt;# Restore from lock file (equivalent of npm ci)&lt;/span&gt;
npx skills experimental_install

&lt;span class="c"&gt;# Sync from node_modules to agent directories&lt;/span&gt;
npx skills experimental_sync

&lt;span class="c"&gt;# Scaffold a new skill&lt;/span&gt;
npx skills init my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;remove&lt;/code&gt; Doesn't Update the Lock File
&lt;/h3&gt;

&lt;p&gt;This is the biggest footgun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills &lt;span class="nb"&gt;rm &lt;/span&gt;microsoft/playwright-cli &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes the skill files from your agent directories, but &lt;strong&gt;leaves the entry in &lt;code&gt;skills-lock.json&lt;/code&gt;&lt;/strong&gt;. The next time someone runs &lt;code&gt;experimental_install&lt;/code&gt;, the skill comes back.&lt;/p&gt;

&lt;p&gt;Workaround:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npx skills remove&lt;/code&gt; as usual&lt;/li&gt;
&lt;li&gt;Manually edit &lt;code&gt;.skills.json&lt;/code&gt; to remove the entry&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;skills-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npx skills update&lt;/code&gt; or &lt;code&gt;add&lt;/code&gt; remaining skills to regenerate a clean lock file&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;experimental_&lt;/code&gt; Prefix is Real
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;experimental_install&lt;/code&gt; and &lt;code&gt;experimental_sync&lt;/code&gt; are genuinely experimental. The &lt;code&gt;sync&lt;/code&gt; command in the current version is not &lt;code&gt;npx skills sync&lt;/code&gt; — it's &lt;code&gt;npx skills experimental_install&lt;/code&gt; to restore from lock file, and &lt;code&gt;npx skills experimental_sync&lt;/code&gt; to sync from node_modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Behavior with npx
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npx skills&lt;/code&gt; may run a cached older version. Force latest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills@latest add &amp;lt;repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For projects where everyone needs the same CLI version, add it as a devDependency:&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;--save-dev&lt;/span&gt; skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;Add to your CI setup to restore skills on each run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore AI agent skills&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx skills experimental_install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures every developer and CI environment uses exactly the same skill versions as defined in &lt;code&gt;skills-lock.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your Own Skill
&lt;/h2&gt;

&lt;p&gt;Any GitHub repo with a &lt;code&gt;SKILL.md&lt;/code&gt; is installable. Create one with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills init my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds a &lt;code&gt;SKILL.md&lt;/code&gt; that you push to GitHub. Anyone can then install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add yourusername/my-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browse existing skills at &lt;a href="https://skills.sh/" rel="noopener noreferrer"&gt;skills.sh&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npx skills&lt;/code&gt; is npm for AI agent capabilities. The mental model maps cleanly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SKILL.md&lt;/code&gt; = &lt;code&gt;index.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.skills.json&lt;/code&gt; = &lt;code&gt;package.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skills-lock.json&lt;/code&gt; = &lt;code&gt;package-lock.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;experimental_install&lt;/code&gt; = &lt;code&gt;npm ci&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;GitHub = npm registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tooling is still experimental — particularly the lock file management on remove — but it's already useful for ensuring consistent AI behavior across team environments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>cli</category>
      <category>agents</category>
    </item>
    <item>
      <title>Deploying a Google ADK Agent to Vertex AI Agent Engine with Terraform</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 30 Mar 2026 12:45:33 +0000</pubDate>
      <link>https://forem.com/toyama0919/deploying-a-google-adk-agent-to-vertex-ai-agent-engine-with-terraform-83b</link>
      <guid>https://forem.com/toyama0919/deploying-a-google-adk-agent-to-vertex-ai-agent-engine-with-terraform-83b</guid>
      <description>&lt;p&gt;Most documentation for Vertex AI Agent Engine focuses on the Python SDK (&lt;code&gt;vertexai.agent_engines.create&lt;/code&gt;). That works fine for one-off deployments, but if you want your agent infrastructure managed declaratively alongside the rest of your GCP resources, Terraform is the right tool.&lt;/p&gt;

&lt;p&gt;This post walks through a complete Terraform setup for deploying a Google ADK agent to Vertex AI Agent Engine using &lt;code&gt;google_vertex_ai_reasoning_engine&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Terraform &amp;gt;= 1.5&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google&lt;/code&gt; or &lt;code&gt;google-beta&lt;/code&gt; provider&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aiplatform.googleapis.com&lt;/code&gt; enabled&lt;/li&gt;
&lt;li&gt;A Google ADK agent wrapped in &lt;code&gt;AdkApp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Agent Engine Deployment Works
&lt;/h2&gt;

&lt;p&gt;The deployment model is straightforward: &lt;strong&gt;tar.gz your source code, base64-encode it, and pass it to the API via &lt;code&gt;inline_source&lt;/code&gt;&lt;/strong&gt;. The runtime handles dependency installation, session management, and streaming — you just provide the entrypoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agent Entrypoint
&lt;/h2&gt;

&lt;p&gt;The key requirement is an &lt;code&gt;AdkApp&lt;/code&gt; instance at the module level. This is what Terraform's &lt;code&gt;entrypoint_object&lt;/code&gt; points to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/myagent/agent.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai.agent_engines&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdkApp&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Returns weather for a given city.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sunny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answers weather questions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s weather question using the get_weather tool.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This is what entrypoint_object references
&lt;/span&gt;&lt;span class="n"&gt;agent_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AdkApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrapping in &lt;code&gt;AdkApp&lt;/code&gt; automatically exposes &lt;code&gt;create_session&lt;/code&gt;, &lt;code&gt;stream_query&lt;/code&gt;, and other ADK methods as callable endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Source Archive
&lt;/h2&gt;

&lt;p&gt;Agent Engine expects a base64-encoded tar.gz containing your source files and a &lt;code&gt;requirements.txt&lt;/code&gt;. Here's a minimal build script that uses only the Python standard library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scripts/build_source.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;project_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project_root&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;src_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;requirements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tarfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileobj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w:gz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;arcname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;tar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arcname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requirements.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base64&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;requirements.txt&lt;/code&gt; lists PyPI package names — the Agent Engine runtime installs them at deploy time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;google-adk&amp;gt;=1.0.0&lt;/span&gt;
&lt;span class="err"&gt;google-cloud-aiplatform[agent_engines]&amp;gt;=1.93.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Terraform Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;provider.tf&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;google-beta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google-beta"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 6.0"&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"google-beta"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"asia-northeast1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wiring the Archive Build
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;external&lt;/code&gt; data source to invoke the build script during &lt;code&gt;terraform plan&lt;/code&gt;/&lt;code&gt;apply&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"external"&lt;/span&gt; &lt;span class="s2"&gt;"agent_source"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/scripts/build_source.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;project_root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/../.."&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 &lt;code&gt;apply&lt;/code&gt; picks up the latest source automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Agent Engine Resource
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_vertex_ai_reasoning_engine"&lt;/span&gt; &lt;span class="s2"&gt;"my_agent"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google-beta&lt;/span&gt;
  &lt;span class="nx"&gt;display_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-agent-${var.env}"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ADK weather agent"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"asia-northeast1"&lt;/span&gt;

  &lt;span class="nx"&gt;spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;agent_framework&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"google-adk"&lt;/span&gt;
    &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service_account_email&lt;/span&gt;

    &lt;span class="nx"&gt;class_methods&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"create_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"get_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"list_sessions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"delete_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nx"&gt;source_code_spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;inline_source&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;source_archive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;external&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent_source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;python_spec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;entrypoint_module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"src.myagent.agent"&lt;/span&gt;
        &lt;span class="nx"&gt;entrypoint_object&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"agent_engine"&lt;/span&gt;
        &lt;span class="nx"&gt;requirements_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"requirements.txt"&lt;/span&gt;
        &lt;span class="nx"&gt;version&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3.12"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;deployment_spec&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;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LOG_LEVEL"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INFO"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;secret_env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"API_TOKEN"&lt;/span&gt;
        &lt;span class="nx"&gt;secret_ref&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;secret&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-api-token"&lt;/span&gt;
          &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"latest"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Block Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Block&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent_framework&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Must be &lt;code&gt;"google-adk"&lt;/code&gt; — tells the runtime which framework to use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;class_methods&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enumerates callable methods; &lt;code&gt;api_mode = "stream"&lt;/code&gt; enables SSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inline_source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Embeds the base64 tar.gz directly — no GCS bucket needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python_spec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies the entrypoint and Python version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deployment_spec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Injects env vars and Secret Manager secrets at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;class_methods&lt;/code&gt; in Detail
&lt;/h3&gt;

&lt;p&gt;Every ADK method you want to expose must be declared explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;class_methods&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="err"&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;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"create_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"get_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"list_sessions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"delete_session"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api_mode&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nx"&gt;api_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stream"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;api_mode = "stream"&lt;/code&gt; makes the method return a Server-Sent Events stream. Only &lt;code&gt;stream_query&lt;/code&gt; needs this — the rest are standard request/response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Provider choice matters.&lt;/strong&gt; &lt;code&gt;google_vertex_ai_reasoning_engine&lt;/code&gt; is available in both &lt;code&gt;google&lt;/code&gt; and &lt;code&gt;google-beta&lt;/code&gt; providers. Make sure the &lt;code&gt;provider&lt;/code&gt; attribute matches whichever you configure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;external&lt;/code&gt; data source re-runs on every plan.&lt;/strong&gt; This is by design — you always get the latest source. If your build script is slow, consider caching or only calling it on &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;entrypoint_module&lt;/code&gt; uses dot notation, not file paths.&lt;/strong&gt; &lt;code&gt;src.myagent.agent&lt;/code&gt; maps to &lt;code&gt;src/myagent/agent.py&lt;/code&gt; in the archive. Match this to your actual directory structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secret Manager secrets must already exist.&lt;/strong&gt; Terraform reads existing secrets via &lt;code&gt;data&lt;/code&gt; sources — it doesn't create them. Provision secrets separately before running &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy and Verify
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export the resource name to call the agent from Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"agent_engine_resource_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_vertex_ai_reasoning_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vertexai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai.agent_engines&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AdkApp&lt;/span&gt;

&lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asia-northeast1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AdkApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_resource_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;projects/my-project/locations/asia-northeast1/reasoningEngines/&amp;lt;ID&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weather in Tokyo?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google_vertex_ai_reasoning_engine&lt;/code&gt; is available in both &lt;code&gt;google&lt;/code&gt; and &lt;code&gt;google-beta&lt;/code&gt; providers&lt;/li&gt;
&lt;li&gt;Source code is delivered as a base64 tar.gz via &lt;code&gt;inline_source&lt;/code&gt; — no GCS required&lt;/li&gt;
&lt;li&gt;A minimal Python script using only stdlib is enough to produce the archive&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;class_methods&lt;/code&gt; must explicitly enumerate every ADK method you want to expose&lt;/li&gt;
&lt;li&gt;Secret Manager integration is declarative via &lt;code&gt;secret_env&lt;/code&gt; blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraforming Agent Engine makes cleanup (&lt;code&gt;terraform destroy&lt;/code&gt;), environment promotion, and drift detection straightforward. The ADK + Terraform combination has sparse documentation, so hopefully this fills the gap.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>gcp</category>
      <category>googlecloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI-Driven Chrome Extension Development with WXT and Chrome DevTools MCP</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:21:39 +0000</pubDate>
      <link>https://forem.com/toyama0919/ai-driven-chrome-extension-development-with-wxt-and-chrome-devtools-mcp-4109</link>
      <guid>https://forem.com/toyama0919/ai-driven-chrome-extension-development-with-wxt-and-chrome-devtools-mcp-4109</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Building a Chrome extension that modifies a third-party web app is a unique challenge. The DOM structure is opaque, class names are minified and change between deployments, and there's no official API to hook into. Traditional extension development looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the DOM manually in DevTools&lt;/li&gt;
&lt;li&gt;Write selectors and content scripts&lt;/li&gt;
&lt;li&gt;Reload the extension&lt;/li&gt;
&lt;li&gt;Check if it works&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This cycle is slow. I wanted an AI coding agent that could &lt;strong&gt;see the actual browser state&lt;/strong&gt; and &lt;strong&gt;verify its own changes&lt;/strong&gt; — not just generate code blindly.&lt;/p&gt;

&lt;p&gt;That's how I arrived at this stack: &lt;strong&gt;WXT&lt;/strong&gt; for the extension framework, &lt;strong&gt;Chrome DevTools MCP&lt;/strong&gt; for giving the AI agent browser access, and &lt;strong&gt;Cursor&lt;/strong&gt; as the IDE tying it all together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://wxt.dev" rel="noopener noreferrer"&gt;WXT&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Chrome extension framework (TypeScript, hot reload, Manifest V3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/nichochar/chrome-devtools-mcp" rel="noopener noreferrer"&gt;Chrome DevTools MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;MCP server that exposes Chrome DevTools Protocol to AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cursor.sh" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;AI-powered IDE with native MCP support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 1: WXT with a Fixed CDP Port
&lt;/h2&gt;

&lt;p&gt;WXT is a framework that wraps Chrome extension development with file-based routing, hot reload, and TypeScript support out of the box. The key insight is that WXT's runner can launch Chrome with &lt;strong&gt;custom Chromium args&lt;/strong&gt; — including &lt;code&gt;--remote-debugging-port&lt;/code&gt;.&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="c1"&gt;// wxt.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&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;wxt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My Extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage&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;extensionApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;chromiumArgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--remote-debugging-port=9222&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`--user-data-dir=&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="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;/.chrome-debug-profile`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--exclude-switches=enable-automation&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;startUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things to note:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--remote-debugging-port=9222&lt;/code&gt;&lt;/strong&gt; — Exposes the Chrome DevTools Protocol on a fixed port. The MCP server connects here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--user-data-dir&lt;/code&gt;&lt;/strong&gt; — A dedicated profile directory, separate from your daily Chrome. Login sessions persist across dev restarts. &lt;strong&gt;Add this to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt; — it contains cookies and session tokens that must not be pushed to a repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--exclude-switches=enable-automation&lt;/code&gt;&lt;/strong&gt; — Without this, some sites detect the "automated" browser and block sign-in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you run &lt;code&gt;wxt&lt;/code&gt; (or &lt;code&gt;npm run dev&lt;/code&gt;), WXT launches Chrome with these args, loads your extension, and watches for file changes — all in one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Chrome DevTools MCP Configuration
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) lets AI agents call external tools. Chrome DevTools MCP is an MCP server that wraps the Chrome DevTools Protocol — giving your AI agent the ability to navigate pages, evaluate JavaScript, take screenshots, and inspect the DOM.&lt;/p&gt;

&lt;p&gt;Configuration lives in &lt;code&gt;.cursor/mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chrome-devtools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"chrome-devtools-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--browserUrl=http://127.0.0.1:9222"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. When Cursor starts, it spins up the MCP server, which connects to Chrome on port 9222. The AI agent can now see and interact with your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The Dev Script (Optional but Recommended)
&lt;/h2&gt;

&lt;p&gt;A small shell script wrapping the two main workflows keeps things ergonomic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dev.sh dev      &lt;span class="c"&gt;# WXT dev server + Chrome (hot reload + MCP)&lt;/span&gt;
./dev.sh start    &lt;span class="c"&gt;# Load built extension (no hot reload, MCP only)&lt;/span&gt;
./dev.sh stop     &lt;span class="c"&gt;# Kill debug Chrome&lt;/span&gt;
./dev.sh status   &lt;span class="c"&gt;# Check CDP connection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dev&lt;/code&gt; is the primary mode — WXT handles everything and you get hot reload. &lt;code&gt;start&lt;/code&gt; is for testing production builds with MCP inspection. The script handles edge cases like port conflicts, PID management, and connection verification via &lt;code&gt;curl http://localhost:9222/json/version&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow in Practice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start the environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev   &lt;span class="c"&gt;# or ./dev.sh dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome opens automatically with the extension loaded. WXT watches for file changes. The MCP server connects to port 9222. Cursor's AI agent can now see the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. AI inspects current state
&lt;/h3&gt;

&lt;p&gt;The AI agent evaluates JavaScript in the browser context to understand the current DOM:&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;// The agent runs this via MCP&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`() =&amp;gt; ({
    injectedStyle: !!document.getElementById('my-extension-styles'),
    buttonCount: document.querySelectorAll('.my-custom-button').length,
    panelVisible: !!document.getElementById('my-panel'),
  })`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent can &lt;strong&gt;verify its own changes without you switching context&lt;/strong&gt;. It writes code, WXT hot-reloads, and the agent checks if the DOM updated correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI verifies changes through the browser
&lt;/h3&gt;

&lt;p&gt;Here's the key difference from normal AI-assisted coding. Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I've added the panel. Please refresh and check if it works."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI does this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I've added the panel. Let me verify... [evaluates script via MCP] ... The &lt;code&gt;#my-panel&lt;/code&gt; element exists, has 5 child entries, and is positioned correctly. Rendering looks good."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Text-based DOM verification is preferred over screenshots — it's faster, cheaper, and more precise:&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;// Good: structured verification&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.my-button&lt;/span&gt;&lt;span class="dl"&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;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;firstButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buttons&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;outerHTML&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Screenshots only when you need visual layout confirmation&lt;/span&gt;
&lt;span class="nf"&gt;take_screenshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tips and Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Content Script Isolation
&lt;/h3&gt;

&lt;p&gt;Chrome extension content scripts run in an &lt;strong&gt;isolated world&lt;/strong&gt;. Variables set on &lt;code&gt;window&lt;/code&gt; in the content script are invisible to &lt;code&gt;evaluate_script&lt;/code&gt; via MCP, because MCP evaluates in the &lt;strong&gt;page context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The workaround: verify through DOM side effects, not global variables.&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;// Won't work: window globals are in the isolated world&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;myExtensionState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// → undefined&lt;/span&gt;

&lt;span class="c1"&gt;// Works: check the DOM changes the extension made&lt;/span&gt;
&lt;span class="nf"&gt;evaluate_script&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;styleInjected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-extension-styles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;panelExists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-panel&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Selector Strategy for Third-Party UIs
&lt;/h3&gt;

&lt;p&gt;When building extensions for sites you don't control, selectors break frequently. A fallback chain helps:&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;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[aria-label*="Submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-test-id="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.submit-btn&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;Priority:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ARIA attributes&lt;/strong&gt; (&lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;role&lt;/code&gt;) — most stable across updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic attributes&lt;/strong&gt; (&lt;code&gt;data-test-id&lt;/code&gt;) — moderately stable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Class names&lt;/strong&gt; — last resort, always provide as fallback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can even build a DOM analyzer shortcut (&lt;code&gt;Ctrl+Shift+D&lt;/code&gt;) that exports the page structure in a format the AI agent can consume. When selectors break, press the shortcut, paste the output into Cursor, and the agent updates the fallback selectors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async DOM Waiting
&lt;/h3&gt;

&lt;p&gt;SPA elements appear asynchronously. Rather than fragile &lt;code&gt;setTimeout&lt;/code&gt; chains, use polling with bounded retries:&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&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;el&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&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;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&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="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the element never appears, fail silently — no console spam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Login Gotcha
&lt;/h3&gt;

&lt;p&gt;When Chrome launches with &lt;code&gt;--remote-debugging-port&lt;/code&gt;, Google sometimes detects it as an "unsafe browser" and blocks sign-in. The &lt;code&gt;--exclude-switches=enable-automation&lt;/code&gt; flag helps, but if it's not enough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Launch Chrome with the dedicated profile (without WXT)&lt;/li&gt;
&lt;li&gt;Sign in manually&lt;/li&gt;
&lt;li&gt;Close Chrome&lt;/li&gt;
&lt;li&gt;Now run &lt;code&gt;npm run dev&lt;/code&gt; — WXT reuses the same profile with the valid session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The dedicated &lt;code&gt;--user-data-dir&lt;/code&gt; persists your login across dev sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;--user-data-dir&lt;/code&gt; and Security
&lt;/h3&gt;

&lt;p&gt;The dedicated profile serves two purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation from your daily Chrome&lt;/strong&gt;: The dev browser doesn't touch your bookmarks, extensions, or sessions — and your personal credentials don't leak into the dev environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal credentials&lt;/strong&gt;: Only log into what you need for development. Don't sign into personal Gmail or other unrelated accounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always add the profile directory to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt;. It contains cookies, session tokens, and LocalStorage.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.chrome-debug-profile/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CDP port 9222 is accessible from localhost&lt;/strong&gt;. &lt;code&gt;--remote-debugging-port&lt;/code&gt; binds to &lt;code&gt;127.0.0.1&lt;/code&gt; by default, but any process on your machine can access all open tabs. Only run it during active development.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't use this on shared machines&lt;/strong&gt;. While CDP is open, anyone on the same machine can control the browser session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you want to try this workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a WXT project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create wxt@latest my-extension
&lt;span class="nb"&gt;cd &lt;/span&gt;my-extension
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add CDP port to &lt;code&gt;wxt.config.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;chromiumArgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--remote-debugging-port=9222&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`--user-data-dir=&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="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;/.chrome-debug-profile`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--exclude-switches=enable-automation&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Create &lt;code&gt;.cursor/mcp.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chrome-devtools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chrome-devtools-mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--browserUrl=http://127.0.0.1:9222"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chrome launches with CDP enabled, Cursor's agent connects. Your AI agent can now see your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Workflow Matters
&lt;/h2&gt;

&lt;p&gt;The traditional Chrome extension development loop is &lt;strong&gt;write → reload → manually check → repeat&lt;/strong&gt;. With WXT + Chrome DevTools MCP, it becomes &lt;strong&gt;write → auto-reload → AI verifies → iterate&lt;/strong&gt; — and the AI agent can do the first and last steps too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt; goes from "read console logs, set breakpoints, manually reproduce" to "AI evaluates scripts in the live browser and reports what's happening."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selector maintenance&lt;/strong&gt; goes from "open DevTools, inspect element, copy selector, paste into code" to "AI reads the DOM and updates fallback selectors."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature development&lt;/strong&gt; goes from "code blind, test manually" to "AI writes code, checks DOM state, fixes issues — all in one turn."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doesn't replace understanding your own extension. But it dramatically shortens the feedback loop, especially for the tedious parts of third-party DOM manipulation.&lt;/p&gt;

</description>
      <category>chromeextension</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cursor</category>
    </item>
    <item>
      <title>BigQuery Global Queries: Join Data Across Regions Without ETL</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Sun, 22 Mar 2026 12:38:06 +0000</pubDate>
      <link>https://forem.com/toyama0919/bigquery-global-queries-join-data-across-regions-without-etl-1ho1</link>
      <guid>https://forem.com/toyama0919/bigquery-global-queries-join-data-across-regions-without-etl-1ho1</guid>
      <description>&lt;p&gt;As of February 2026, Google released &lt;strong&gt;BigQuery Global Queries&lt;/strong&gt; in Preview. It lets you join tables from completely different geographic regions — say, &lt;code&gt;asia-northeast1&lt;/code&gt; (Tokyo) and &lt;code&gt;us-central1&lt;/code&gt; (Iowa) — in a &lt;strong&gt;single SQL statement&lt;/strong&gt;. No ETL, no data movement pipelines, no manual copying.&lt;/p&gt;

&lt;p&gt;This post covers how it actually works under the hood, what it costs, and the gotchas you need to know before using it in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old Problem
&lt;/h2&gt;

&lt;p&gt;BigQuery historically required all datasets referenced in a single query to live in the same location. If your sales data was in Tokyo and your user master was in the US, you had two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy one dataset to the other region (ETL pipeline, operational overhead).&lt;/li&gt;
&lt;li&gt;Run two separate queries and join the results in application code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Global Queries eliminates this constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works: 4-Stage Execution
&lt;/h2&gt;

&lt;p&gt;When you run a global query, BigQuery orchestrates the execution across regions transparently:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Distributed Execution
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Query Optimizer&lt;/strong&gt; analyzes the query, identifies which tables live in which regions, and assigns the querying region as the &lt;strong&gt;Primary Region&lt;/strong&gt; (the "leader"). Workers in each remote region receive their execution assignments in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data Pushdown
&lt;/h3&gt;

&lt;p&gt;This is the most critical stage — and the one that makes global queries economically viable.&lt;/p&gt;

&lt;p&gt;Before any data crosses the network, BigQuery applies three types of pushdown to minimize transfer size:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Predicate Pushdown&lt;/strong&gt;: &lt;code&gt;WHERE&lt;/code&gt; clause filters run &lt;em&gt;in the remote region&lt;/em&gt;, before the data moves. A 100M-row table filtered to 100 rows transfers 100 rows — not 100M.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projection Pushdown&lt;/strong&gt;: Only the columns named in &lt;code&gt;SELECT&lt;/code&gt; are read from remote storage. BigQuery's columnar storage (Capacitor) makes this efficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregation Pushdown&lt;/strong&gt;: &lt;code&gt;GROUP BY&lt;/code&gt;/&lt;code&gt;SUM&lt;/code&gt;/&lt;code&gt;COUNT&lt;/code&gt; operations run as partial aggregations in the remote region. A billion-row transaction table can be summarized to 365 rows (daily totals) before transfer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Data Transfer
&lt;/h3&gt;

&lt;p&gt;Filtered, minimized results travel over Google's internal network to the Primary Region, where they're stored in &lt;strong&gt;temporary internal tables&lt;/strong&gt; for up to 8 hours. This is where cross-region &lt;strong&gt;egress charges&lt;/strong&gt; are incurred.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Final Join
&lt;/h3&gt;

&lt;p&gt;The Primary Region merges local data with the temporary remote data, as if everything were in one place. The query result returned to the user looks like any normal BigQuery result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Executed from asia-northeast1 (Tokyo)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_global_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.japan_dataset.sales`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;   &lt;span class="c1"&gt;-- local&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.sales`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;      &lt;span class="c1"&gt;-- remote (auto-transferred)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-01'&lt;/span&gt;               &lt;span class="c1"&gt;-- pushed down to both regions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  IAM Permissions
&lt;/h2&gt;

&lt;p&gt;Global Queries require two layers of setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project-level opt-in (admin task)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enable execution from the primary region&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PROJECT&lt;/span&gt; &lt;span class="nv"&gt;`your-project-id`&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`region-asia-northeast1.enable_global_queries_execution`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Enable data access from the remote region&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="n"&gt;PROJECT&lt;/span&gt; &lt;span class="nv"&gt;`your-project-id`&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`region-us-central1.enable_global_queries_data_access`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User-level permissions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bigquery.jobs.createGlobalQuery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required to initiate a global query. Currently only included in &lt;code&gt;roles/bigquery.admin&lt;/code&gt; — create a custom role for regular users.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roles/bigquery.dataViewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required on every dataset being referenced, in every region.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Cost Structure
&lt;/h2&gt;

&lt;p&gt;Global queries have &lt;strong&gt;three billing components&lt;/strong&gt; instead of the usual one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;th&gt;Approximate Price (2026)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bytes scanned across all regions&lt;/td&gt;
&lt;td&gt;$6.25 / 1 TB (on-demand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Egress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data transferred from remote to primary region&lt;/td&gt;
&lt;td&gt;~$0.08–$0.12 / 1 GB (intercontinental)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Temporary Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Intermediate data stored for up to 8 hours&lt;/td&gt;
&lt;td&gt;~$0.02/GB-month (prorated)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cost simulation
&lt;/h3&gt;

&lt;p&gt;Scenario: Query from Tokyo, scanning a 1 TB table in &lt;code&gt;us-central1&lt;/code&gt;, with a &lt;code&gt;WHERE&lt;/code&gt; clause that reduces the data transferred to 1 GB.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute: 1 TB × $6.25 = &lt;strong&gt;$6.25&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Egress: 1 GB × $0.12 = &lt;strong&gt;$0.12&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: ~$6.37&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you skip the &lt;code&gt;WHERE&lt;/code&gt; clause and transfer the full 1 TB: egress alone exceeds &lt;strong&gt;$100&lt;/strong&gt;. Pushdown is not optional — it's the entire cost model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dry run before executing
&lt;/h3&gt;

&lt;p&gt;Use the BigQuery Console (it shows estimated bytes scanned before you click Run) or the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bq query &lt;span class="nt"&gt;--dry_run&lt;/span&gt; &lt;span class="nt"&gt;--use_legacy_sql&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="s1"&gt;'SELECT ...'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: As of the current preview, dry runs may not accurately estimate egress (only compute bytes). Budget conservatively.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Latency
&lt;/h3&gt;

&lt;p&gt;Cross-region queries are &lt;strong&gt;always slower&lt;/strong&gt; than single-region queries. Physical distance adds hundreds of milliseconds of network latency, plus multi-region orchestration overhead. Expect a minimum of &lt;strong&gt;5–10 seconds&lt;/strong&gt; even for modest cross-region joins. Real-time dashboards are not a good fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Residency
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Primary Region is where remote data lands temporarily&lt;/strong&gt;. If GDPR or local privacy laws prohibit data from Region A leaving Region A, you must run the query &lt;em&gt;from&lt;/em&gt; Region A as the primary — not from a region outside it. VPC Service Controls perimeters are also respected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Limitations (Preview, March 2026)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No Query Cache
&lt;/h3&gt;

&lt;p&gt;Global queries never use the query cache. Since data can change in any remote region at any time, BigQuery always reads fresh data. Every execution incurs full compute and egress costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workaround&lt;/strong&gt;: For frequently-used cross-region joins, materialize results into a local table using &lt;code&gt;CREATE TABLE AS SELECT&lt;/code&gt; and query that instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  No INFORMATION_SCHEMA from Remote Regions
&lt;/h3&gt;

&lt;p&gt;You cannot query &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt; views from a remote region within a global query. Joining metadata across regions requires first exporting that metadata into regular tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unsupported Table Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BigLake Apache Iceberg tables&lt;/strong&gt; in remote regions are not supported as remote sources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partition pseudo-columns&lt;/strong&gt; (&lt;code&gt;_PARTITIONTIME&lt;/code&gt;, &lt;code&gt;_PARTITIONDATE&lt;/code&gt;) may not pushdown correctly (more on this below).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No Sandbox Support
&lt;/h3&gt;

&lt;p&gt;Billing Account required. The Sandbox (free tier) does not support Global Queries because egress charges can exceed the free quota.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Partition Pseudo-Column Trap
&lt;/h2&gt;

&lt;p&gt;This is the most dangerous limitation in production, and deserves its own section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background: Pseudo-columns vs. Physical columns
&lt;/h3&gt;

&lt;p&gt;BigQuery offers two partitioning strategies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Partition Key&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ingestion-time partitioned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Arrival timestamp, managed by BigQuery&lt;/td&gt;
&lt;td&gt;Via &lt;code&gt;_PARTITIONTIME&lt;/code&gt; / &lt;code&gt;_PARTITIONDATE&lt;/code&gt; (pseudo-columns)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Column-based partitioned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An actual column in your table schema (e.g., &lt;code&gt;event_date&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Via the column name directly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pseudo-columns are not part of the formal table schema. They're metadata-level constructs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why pushdown fails for pseudo-columns
&lt;/h3&gt;

&lt;p&gt;When the Query Optimizer sends execution instructions to a remote region, it works from the table's schema definition. Pseudo-columns aren't in that definition, so the optimizer can't reliably communicate partition pruning constraints to the remote worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worst case&lt;/strong&gt;: A filter like &lt;code&gt;WHERE _PARTITIONDATE = '2026-03-01'&lt;/code&gt; is silently ignored in the remote region. The remote worker scans the entire table across all partitions and begins transferring everything to the primary region. Your query either times out or generates a very large bill.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: Migrate to column-based partitioning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a new table with an explicit physical partition column&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`project.dataset.new_table`&lt;/span&gt;
&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;event_date&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_PARTITIONDATE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;event_date&lt;/span&gt;  &lt;span class="c1"&gt;-- materialize the pseudo-column&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.dataset.old_table`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a physical column, the optimizer sees it in the schema, understands the partition structure, and confidently applies pushdown in the remote region.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workaround B: Aliasing via Views (use with caution)
&lt;/h3&gt;

&lt;p&gt;If migrating the table isn't possible, you can create a view in the remote region that aliases the pseudo-column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- View in us-central1&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.v_sales`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;_PARTITIONDATE&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;partition_date_col&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.ingestion_time_partitioned_table`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then query the view from the primary region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`project.us_dataset.v_sales`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;partition_date_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-03-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;em&gt;sometimes&lt;/em&gt; works for simple queries, but pushdown is not guaranteed. In complex queries with JOINs or aggregations, the optimizer often loses the connection between the aliased column and the underlying partition structure, falls back to full-scan, and transfers everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always verify&lt;/strong&gt; that pushdown is working by checking the Query Execution Plan and confirming the remote &lt;code&gt;READ&lt;/code&gt; stage shows filtered row counts — not the full table row count.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operational Best Practices
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No query cache&lt;/td&gt;
&lt;td&gt;Materialize frequent cross-region joins into local intermediate tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need metadata across regions&lt;/td&gt;
&lt;td&gt;Export metadata to regular tables on a schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion-time partitioned tables&lt;/td&gt;
&lt;td&gt;Migrate to column-based partitioning before using as remote sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unclear cost pre-execution&lt;/td&gt;
&lt;td&gt;Use dry run + estimate egress separately; add a buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;BigQuery Global Queries is a genuinely useful feature that eliminates an entire category of ETL pipelines. The execution model is well-designed — pushdown at the predicate, projection, and aggregation levels means you're typically only transferring the data you actually need.&lt;/p&gt;

&lt;p&gt;The key things to internalize:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pushdown is the cost model.&lt;/strong&gt; Filter early, select only the columns you need, push aggregations to the remote side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion-time partitioned tables are a liability&lt;/strong&gt; in global queries. Migrate to column-based partitioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's Preview&lt;/strong&gt; — no query cache, no &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt; cross-region, no BigLake Iceberg remotes. Design your architecture around these constraints.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check the &lt;a href="https://cloud.google.com/bigquery/docs/global-queries" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; for the latest changes as this feature moves toward GA.&lt;/p&gt;

</description>
      <category>bigquery</category>
      <category>gcp</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
    <item>
      <title>The Cloud is No Longer Virtual: The Harsh Physical Reality of AI Infra in 2026</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 23 Feb 2026 08:42:02 +0000</pubDate>
      <link>https://forem.com/toyama0919/the-cloud-is-no-longer-virtual-the-harsh-physical-reality-of-ai-infra-in-2026-27ba</link>
      <guid>https://forem.com/toyama0919/the-cloud-is-no-longer-virtual-the-harsh-physical-reality-of-ai-infra-in-2026-27ba</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The "Virtual" in Cloud is fading. In 2026, AI infrastructure is dominated by three physical constraints: &lt;strong&gt;power grid capacity&lt;/strong&gt;, &lt;strong&gt;tax legislations&lt;/strong&gt;, and &lt;strong&gt;liquid cooling&lt;/strong&gt;. If you are still picking regions based solely on latency, you are overpaying by at least 20%.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Death of the "Sales Tax Holiday"
&lt;/h2&gt;

&lt;p&gt;For a decade, states like Virginia attracted data centers with massive sales tax exemptions. That era ended in February 2026 with &lt;strong&gt;Virginia HB 897&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters for your bill:
&lt;/h3&gt;

&lt;p&gt;In the US, "Sales Tax" works differently from Japan's VAT or Europe's VAT. It is a &lt;strong&gt;sunken cost&lt;/strong&gt; with no tax credit for businesses. When a state removes a 6-10% tax exemption on hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An NVIDIA B200 cluster worth $100M suddenly costs &lt;strong&gt;$110M&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;This extra Capex is directly passed to you as higher hourly instance rates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Move:&lt;/strong&gt; We are seeing a "Great Migration" to the &lt;strong&gt;Midwest AI Belt&lt;/strong&gt; (Indiana, Ohio, Iowa), where 20-30 year tax holidays are still guaranteed. &lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why "Power" is the New "Latency"
&lt;/h2&gt;

&lt;p&gt;We used to care about milliseconds. Now, we care about Megawatts. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Virginia Gridlock
&lt;/h3&gt;

&lt;p&gt;In North Virginia (&lt;code&gt;us-east-1&lt;/code&gt;), data centers now consume over &lt;strong&gt;25% of the total state power&lt;/strong&gt;. The grid is saturated. To build new AI capacity, AWS and Google are now forced to become &lt;strong&gt;Energy Producers&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nuclear is the New "Default Gateway"
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SMRs (Small Modular Reactors):&lt;/strong&gt; AWS is deploying SMRs as "Microservices for Energy"—factory-built reactors that can be dropped next to a data center.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct-to-Plant:&lt;/strong&gt; Microsoft and Azure are restarting decommissioned plants (like Three Mile Island) just to keep their GPUs humming.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. The "Jevons Paradox" of NVIDIA GPUs
&lt;/h2&gt;

&lt;p&gt;People often ask: &lt;em&gt;"Why doesn't NVIDIA make low-power GPUs?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;Tokens per Watt&lt;/strong&gt;. NVIDIA's Blackwell (B200) consumes a massive &lt;strong&gt;1,200W&lt;/strong&gt;, but it is &lt;strong&gt;25x more efficient&lt;/strong&gt; at generating tokens than the previous generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Thermal Wall
&lt;/h3&gt;

&lt;p&gt;Because one rack now pulls &lt;strong&gt;120kW+&lt;/strong&gt;, traditional air cooling is dead. 2026 is the year of &lt;strong&gt;Liquid Cooling&lt;/strong&gt;. If your DC doesn't have pipes, it can't run the latest AI models. This creates a "Performance Gap" between old regions and new AI-native regions.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Tokyo Context: Why so expensive?
&lt;/h2&gt;

&lt;p&gt;Many Japanese developers wonder why &lt;code&gt;ap-northeast-1&lt;/code&gt; costs more than &lt;code&gt;us-east-1&lt;/code&gt; despite Japan's "cheaper" cost of living.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Imported Energy:&lt;/strong&gt; Japan's industrial electricity is &lt;strong&gt;2-3x more expensive&lt;/strong&gt; than the US.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dollar-Denominated Silicon:&lt;/strong&gt; Everything from the GPU to the fuel for the power plant is priced in USD. The weak Yen makes these "imported" cloud resources luxury items.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Humidity:&lt;/strong&gt; Tokyo’s humid summers make PUE (Power Usage Effectiveness) worse than the dry, flat plains of Ohio.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  5. FinOps 2026: Actions for Engineers
&lt;/h2&gt;

&lt;p&gt;"Turning off idle instances" is FinOps 101. To be a Senior Infrastructure Engineer in 2026, you need &lt;strong&gt;Regional Arbitrage&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Move Training to the Midwest:&lt;/strong&gt; Shift non-latency-sensitive training jobs from &lt;code&gt;us-east-1&lt;/code&gt; to &lt;code&gt;us-west-2&lt;/code&gt; (Oregon) or the new Indiana regions to save &lt;strong&gt;10-15%&lt;/strong&gt; on tax and power alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Token-Specific Hardware:&lt;/strong&gt; Evaluate &lt;strong&gt;TPU v7&lt;/strong&gt; (Google Cloud) or &lt;strong&gt;Trainium 2&lt;/strong&gt; (AWS). In 2026, specialized ASICs are often &lt;strong&gt;3x more cost-effective&lt;/strong&gt; than general-purpose GPUs for specific LLM workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code (IaC) for Regions:&lt;/strong&gt; Don't hardcode regions. Use variables that allow you to follow the "Tax-Free Energy" across the globe.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The cloud is no longer an invisible layer of abstraction. It is a physical plant that breathes energy and exhales heat. The best engineers in 2026 will be those who understand the &lt;strong&gt;physics and economics&lt;/strong&gt; behind the API call.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What are your thoughts?&lt;/strong&gt; Are you planning to migrate your workloads out of Virginia? Let's discuss in the comments!&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>ai</category>
      <category>aws</category>
      <category>finops</category>
    </item>
    <item>
      <title>Track Your Azure OpenAI Costs in Seconds, Not Minutes</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Mon, 26 Jan 2026 12:32:19 +0000</pubDate>
      <link>https://forem.com/toyama0919/track-your-azure-openai-costs-in-seconds-not-minutes-2fnb</link>
      <guid>https://forem.com/toyama0919/track-your-azure-openai-costs-in-seconds-not-minutes-2fnb</guid>
      <description>&lt;p&gt;If you're building AI applications with Azure OpenAI, you know the drill: costs can spiral fast. One experimental feature using o1-preview, a few hundred test runs, and suddenly your bill looks very different from last month.&lt;/p&gt;

&lt;p&gt;The Azure portal shows you the numbers eventually, but when you're iterating quickly on AI features, you need real-time visibility. That's exactly what &lt;code&gt;azurecost&lt;/code&gt; delivers - instant Azure OpenAI cost tracking from your terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Azure OpenAI Cost Challenge
&lt;/h2&gt;

&lt;p&gt;Unlike traditional cloud services with predictable pricing, Azure OpenAI costs vary wildly based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model choice (GPT-4o-mini vs o1-preview is a 5-15x difference)&lt;/li&gt;
&lt;li&gt;Token usage (both prompt and completion tokens)&lt;/li&gt;
&lt;li&gt;Deployment scaling and throughput&lt;/li&gt;
&lt;li&gt;Testing and development cycles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The questions you need answered daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How much did my o1-preview deployment cost yesterday?"&lt;/li&gt;
&lt;li&gt;"Which resource group is burning through credits?"&lt;/li&gt;
&lt;li&gt;"Did that new feature spike my OpenAI spend?"&lt;/li&gt;
&lt;li&gt;"How does dev environment cost compare to production?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Checking this through the Azure portal means multiple clicks, page loads, and waiting. When you're checking costs multiple times a day during active development, this friction adds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azurecost&lt;/code&gt; is a Python CLI tool built specifically for developers who need fast answers about their Azure spending. For Azure OpenAI users, it's the fastest way to track Cognitive Services costs without touching the Azure portal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Works for Azure OpenAI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instant visibility&lt;/strong&gt;: See your OpenAI costs in 2 seconds, not 2 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily granularity&lt;/strong&gt;: Catch cost spikes the day they happen, not at month-end&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-level tracking&lt;/strong&gt;: Monitor individual Azure OpenAI accounts separately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource group isolation&lt;/strong&gt;: Separate dev, staging, and production costs effortlessly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-dimensional views&lt;/strong&gt;: Break down by service, location, resource group, or resource ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation-ready&lt;/strong&gt;: Python API for integrating into your CI/CD or daily reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-currency&lt;/strong&gt;: Works with any billing currency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Installation takes one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log in with Azure CLI (if you haven't already):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your Azure OpenAI costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; your-subscription-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                 2025-11    2025-12
&lt;span class="nt"&gt;------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                 1247.83    2891.45
Cognitive Services    1247.83    2891.45
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's your Azure OpenAI spend right there - Cognitive Services is the billing category for Azure OpenAI. Notice the spike in December? Now you can investigate what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Azure OpenAI Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario 1: Daily Cost Monitoring During Development
&lt;/h3&gt;

&lt;p&gt;You're building a new reasoning agent with o1-preview. Check costs every morning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; prod-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;           2025-12-15  2025-12-16  2025-12-17  2025-12-18
&lt;span class="nt"&gt;------------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;  &lt;span class="nt"&gt;-----------&lt;/span&gt;
total                45.23        52.18       178.45        51.20
Cognitive Services   45.23        52.18       178.45        51.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoa, December 17th spiked to $178. That's the day you started load testing with o1-preview. Now you know exactly when and how much it costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2: Environment-Based Cost Breakdown
&lt;/h3&gt;

&lt;p&gt;You have separate resource groups for dev, staging, and production. See costs side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceGroup &lt;span class="nt"&gt;-d&lt;/span&gt; ServiceName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                                        2025-11    2025-12
&lt;span class="nt"&gt;-----------------------------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                                        1247.83    2891.45
ai-dev-rg/Cognitive Services                  342.15     456.32
ai-staging-rg/Cognitive Services              198.42     287.89
ai-prod-rg/Cognitive Services                 707.26    2147.24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production jumped from $707 to $2147. Time to optimize those prompts or consider GPT-4o-mini for some use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3: Focused Investigation on Production
&lt;/h3&gt;

&lt;p&gt;Something's wrong with production costs. Drill down to just that resource group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-r&lt;/span&gt; ai-prod-rg &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See two weeks of daily costs for production only. Spot the pattern, correlate with deployments or feature releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 4: Multi-Region Cost Analysis
&lt;/h3&gt;

&lt;p&gt;Running Azure OpenAI deployments in multiple regions? Group by location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; global-ai-sub &lt;span class="nt"&gt;-d&lt;/span&gt; Location &lt;span class="nt"&gt;-d&lt;/span&gt; ServiceName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                                   2025-11    2025-12
&lt;span class="nt"&gt;------------------------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                                   1247.83    2891.45
East US/Cognitive Services               823.14    1923.87
West Europe/Cognitive Services           424.69     967.58
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;East US is handling most of the load. Maybe redistribute traffic or consider regional pricing differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 5: Resource-Level Cost Analysis
&lt;/h3&gt;

&lt;p&gt;Running multiple Azure OpenAI accounts for different teams or use cases? Track costs at the individual resource level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;USD&lt;span class="o"&gt;)&lt;/span&gt;                                                                                     2025-12    2026-01
&lt;span class="nt"&gt;--------------------------------------------------------------------------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                                                                                     5741.44   16571.60
/resourcegroups/ai/providers/microsoft.cognitiveservices/accounts/chatbot-prod           3401.80   16390.44
/resourcegroups/ai/providers/microsoft.cognitiveservices/accounts/analytics-engine       2194.17     131.82
/resourcegroups/ai/providers/microsoft.cognitiveservices/accounts/internal-tools          145.47      49.34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows exactly which Azure OpenAI account is consuming credits. Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost attribution&lt;/strong&gt;: Charge back costs to specific teams or projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifying cost anomalies&lt;/strong&gt;: Spot which deployment suddenly increased spend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant environments&lt;/strong&gt;: Track costs per customer or tenant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget allocation&lt;/strong&gt;: Distribute budget based on actual usage patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combine with daily granularity to investigate when a specific resource started costing more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceId &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters for Azure OpenAI Users
&lt;/h2&gt;

&lt;p&gt;Building with Azure OpenAI is different from traditional cloud infrastructure. With VMs or databases, costs are fairly predictable. But with modern language models like GPT-4o and o1-preview, costs depend on how users interact with your application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long conversations = more tokens = higher costs&lt;/li&gt;
&lt;li&gt;Complex reasoning tasks = more input tokens&lt;/li&gt;
&lt;li&gt;Detailed responses = more completion tokens&lt;/li&gt;
&lt;li&gt;Testing and iteration = multiplied costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During active development, you need to check costs frequently. Not once a month when the bill arrives, but daily or even multiple times a day.&lt;/p&gt;

&lt;p&gt;The Azure portal workflow kills this feedback loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open browser&lt;/li&gt;
&lt;li&gt;Navigate to Azure portal (wait for load)&lt;/li&gt;
&lt;li&gt;Find the right subscription&lt;/li&gt;
&lt;li&gt;Click through to Cost Management&lt;/li&gt;
&lt;li&gt;Configure time range and filters&lt;/li&gt;
&lt;li&gt;Wait for data to render&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the time you see the numbers, you've burned 2-3 minutes. When you're doing this multiple times daily, the friction discourages you from checking at all.&lt;/p&gt;

&lt;p&gt;Then you're surprised at month-end when the bill is 3x what you expected.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;azurecost&lt;/code&gt; fixes this. Checking costs becomes as fast as checking git status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two seconds. Real-time feedback. No context switching.&lt;/p&gt;

&lt;p&gt;This speed changes behavior. When checking costs is instant, you actually do it. You catch issues early. You experiment with confidence because you're monitoring the impact.&lt;/p&gt;

&lt;p&gt;The tool emerged from my own need while building AI features. I was spending too much time in the portal doing the same query repeatedly. I wanted something terminal-based that integrated into my development workflow.&lt;/p&gt;

&lt;p&gt;What started as a personal script evolved into a proper tool that my team adopted, then others in the community found useful. The philosophy is simple: do one thing well - show Azure costs fast and clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate Azure OpenAI Cost Monitoring
&lt;/h2&gt;

&lt;p&gt;The Python API lets you integrate cost tracking into your workflows. Send daily Azure OpenAI cost reports to Slack:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# Get yesterday's costs
&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Azurecost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;granularity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DAILY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ResourceGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServiceName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;subscription_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ai-production&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert_tabulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Send to Slack
&lt;/span&gt;&lt;span class="n"&gt;webhook_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤖 Azure OpenAI Cost Report (Last 7 Days)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;```
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webhook_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this as a daily cron job or GitHub Action. Your team gets automatic cost visibility without anyone checking the portal.&lt;/p&gt;

&lt;p&gt;Other use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Budget alerts&lt;/strong&gt;: Trigger warnings when daily costs exceed thresholds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost attribution&lt;/strong&gt;: Track which team or feature is using OpenAI credits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-deployment checks&lt;/strong&gt;: Validate costs before promoting to production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finance automation&lt;/strong&gt;: Generate reports for accounting without manual exports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Tips for AI Workloads
&lt;/h2&gt;

&lt;p&gt;Set environment variables to streamline your daily checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add to your ~/.zshrc or ~/.bashrc&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-ai-subscription-id
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ai-prod-rg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run &lt;code&gt;azurecost&lt;/code&gt; with no arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create shell aliases for common queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Daily OpenAI costs&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;aicosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -g DAILY -a 7'&lt;/span&gt;

&lt;span class="c"&gt;# Production environment only&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;prodcosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -r ai-prod-rg -g DAILY -a 7'&lt;/span&gt;

&lt;span class="c"&gt;# Compare all environments&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;envcosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -d ResourceGroup'&lt;/span&gt;

&lt;span class="c"&gt;# Resource-level breakdown&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;resourcecosts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'azurecost -s ai-subscription -d ResourceId'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;aicosts&lt;/code&gt; each morning as part of your routine. Takes 2 seconds, keeps you informed. Use &lt;code&gt;resourcecosts&lt;/code&gt; when you need to drill down to individual Azure OpenAI accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Azure OpenAI Cost Management
&lt;/h2&gt;

&lt;p&gt;Based on using &lt;code&gt;azurecost&lt;/code&gt; with AI workloads, here are patterns that work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Daily morning check&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;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-sub &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Catch anomalies before they compound. One day of unexpected costs is manageable. A month is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Before and after feature releases&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;&lt;span class="c"&gt;# Before deployment&lt;/span&gt;
azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-sub &lt;span class="nt"&gt;-r&lt;/span&gt; ai-prod-rg &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 3

&lt;span class="c"&gt;# Deploy new feature&lt;/span&gt;

&lt;span class="c"&gt;# After 24 hours&lt;/span&gt;
azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; ai-sub &lt;span class="nt"&gt;-r&lt;/span&gt; ai-prod-rg &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measure the cost impact of new features. Make data-driven decisions about o1-preview vs GPT-4o-mini.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Set up automated alerts&lt;/strong&gt;&lt;br&gt;
Use the Python API to send daily reports. Don't rely on remembering to check manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Isolate environments&lt;/strong&gt;&lt;br&gt;
Use separate resource groups for dev, staging, production. Makes cost attribution trivial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Monitor during load testing&lt;/strong&gt;&lt;br&gt;
Run &lt;code&gt;azurecost&lt;/code&gt; before and after load tests. Understand your cost-per-request at scale before going live.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start Tracking Your Azure OpenAI Costs Now
&lt;/h2&gt;

&lt;p&gt;Install the tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your costs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; your-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You now have instant visibility into your Azure OpenAI spending.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for full documentation, API examples, and to report issues or contribute.&lt;/p&gt;

&lt;p&gt;Building AI features is expensive enough. Don't let invisible costs surprise you. Make cost visibility effortless.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;toyama0919/azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PyPI: &lt;a href="https://pypi.org/project/azurecost/" rel="noopener noreferrer"&gt;azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python 3.8+ required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building with Azure OpenAI? Drop a comment on how you're managing costs or share your use case. Always looking for feedback and ideas.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>openai</category>
      <category>cli</category>
      <category>python</category>
    </item>
    <item>
      <title>ty: The Blazingly Fast Python Type Checker from Astral (Ruff &amp; uv Creators)</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Wed, 07 Jan 2026 04:44:52 +0000</pubDate>
      <link>https://forem.com/toyama0919/ty-the-blazingly-fast-python-type-checker-from-astral-ruff-uv-creators-5bd</link>
      <guid>https://forem.com/toyama0919/ty-the-blazingly-fast-python-type-checker-from-astral-ruff-uv-creators-5bd</guid>
      <description>&lt;h1&gt;
  
  
  ty: The Blazingly Fast Python Type Checker from Astral
&lt;/h1&gt;

&lt;p&gt;Astral, the company behind popular Python tools like &lt;a href="https://github.com/astral-sh/ruff" rel="noopener noreferrer"&gt;Ruff&lt;/a&gt; and &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt;, has released a new type checker called &lt;strong&gt;ty&lt;/strong&gt;. Built in Rust, it promises to revolutionize Python type checking with incredible speed and powerful features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚡ Blazingly Fast&lt;/strong&gt;: 10-100x faster than mypy and Pyright&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔍 Rich Diagnostics&lt;/strong&gt;: Detailed error messages with multi-file context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🧠 Smart Type Inference&lt;/strong&gt;: Catches bugs even without type annotations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎯 Advanced Type System&lt;/strong&gt;: Intersection types, advanced narrowing, reachability analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🛠️ Language Server&lt;/strong&gt;: Built-in support for IDE integration (VS Code extension available)&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using uv (recommended)&lt;/span&gt;
uv tool &lt;span class="nb"&gt;install &lt;/span&gt;ty@latest

&lt;span class="c"&gt;# Using pip&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;ty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check a file&lt;/span&gt;
ty check file.py

&lt;span class="c"&gt;# Check directories&lt;/span&gt;
ty check src tests

&lt;span class="c"&gt;# Verbose mode&lt;/span&gt;
ty check src &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Makes ty Special?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Speed That Matters
&lt;/h3&gt;

&lt;p&gt;ty can type-check the entire Home Assistant project in &lt;strong&gt;~2.19 seconds&lt;/strong&gt;, compared to mypy's &lt;strong&gt;45.66 seconds&lt;/strong&gt;. That's over &lt;strong&gt;20x faster&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;For large codebases, this speed difference transforms the developer experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more waiting for CI to finish&lt;/li&gt;
&lt;li&gt;Instant feedback in your IDE&lt;/li&gt;
&lt;li&gt;Practical to run on every save&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Powerful Type Inference
&lt;/h3&gt;

&lt;p&gt;Here's the magic: &lt;strong&gt;ty can find bugs even without type annotations&lt;/strong&gt;. Let's look at some examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Bug Detection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: Type Mismatches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Oops! Assigning int to str
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty's output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-assignment]: Object of type `int` is not assignable to `str`
 --&amp;gt; example.py:4:9
  |
4 | result: str = add(1, 2)
  |         ---   ^^^^^^^^^ Incompatible value of type `int`
  |         |
  |         Declared type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 2: Missing Attributes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;  &lt;span class="c1"&gt;# email was never defined!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty catches this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[unresolved-attribute]: Object of type `Self@get_email` has no attribute `email`
 --&amp;gt; example.py:6:16
  |
6 |         return self.email
  |                ^^^^^^^^^^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works even for complex cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Typo: should be .execute(), not .exec()
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ty will warn you that &lt;code&gt;.exec()&lt;/code&gt; doesn't exist on the connection object!&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Incorrect Function Arguments
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;four&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# String in int list!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty's error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-argument-type]: Argument to bound method `append` is incorrect
 --&amp;gt; example.py:2:16
  |
2 | numbers.append("four")
  |                ^^^^^^ Expected `int`, found `Literal["four"]`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 4: Null Safety
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# What if value is None?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty spots the issue:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-argument-type]: Expected `Sized`, found `str | None`
 --&amp;gt; example.py:2:16
  |
2 |     return len(value)
  |                ^^^^^
  |
info: Element `None` of this union is not assignable to `Sized`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 5: Implicit None Returns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# Missing return statement - implicit None!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty catches this too:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error[invalid-return-type]: Function can implicitly return `None`,
which is not assignable to return type `str`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;ty uses &lt;code&gt;pyproject.toml&lt;/code&gt; for configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.ty]&lt;/span&gt;
&lt;span class="nn"&gt;[tool.ty.environment]&lt;/span&gt;
&lt;span class="py"&gt;python-version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3.10"&lt;/span&gt;

&lt;span class="nn"&gt;[tool.ty.src]&lt;/span&gt;
&lt;span class="py"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tests/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;exclude&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;".tox/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"build/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dist/**"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Per-directory overrides&lt;/span&gt;
&lt;span class="nn"&gt;[[tool.ty.overrides]]&lt;/span&gt;
&lt;span class="py"&gt;include&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tests/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;[tool.ty.overrides.rules]&lt;/span&gt;
&lt;span class="py"&gt;invalid-assignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ignore"&lt;/span&gt;  &lt;span class="c"&gt;# More lenient for tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Type Check&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;typecheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.10'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install ty&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run type checker&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty check src tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  tox Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[testenv:ty]&lt;/span&gt;
&lt;span class="py"&gt;deps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;ty&lt;/span&gt;
    &lt;span class="err"&gt;-e&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="py"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="err"&gt;ty&lt;/span&gt; &lt;span class="err"&gt;check&lt;/span&gt; &lt;span class="err"&gt;src&lt;/span&gt; &lt;span class="err"&gt;tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  pre-commit Hook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty type checker&lt;/span&gt;
        &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ty check src tests&lt;/span&gt;
        &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
        &lt;span class="na"&gt;pass_filenames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Editor Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  VS Code
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install the "ty" extension from VS Code Marketplace&lt;/li&gt;
&lt;li&gt;Enjoy automatic:

&lt;ul&gt;
&lt;li&gt;Type checking as you type&lt;/li&gt;
&lt;li&gt;Inlay hints for inferred types&lt;/li&gt;
&lt;li&gt;Code actions and quick fixes&lt;/li&gt;
&lt;li&gt;Jump to definition with type awareness&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ty vs mypy vs Pyright
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ty&lt;/th&gt;
&lt;th&gt;mypy&lt;/th&gt;
&lt;th&gt;Pyright&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;⚡⚡⚡ Ultra-fast&lt;/td&gt;
&lt;td&gt;🐢 Moderate&lt;/td&gt;
&lt;td&gt;🚀 Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Messages&lt;/td&gt;
&lt;td&gt;📝 Very detailed&lt;/td&gt;
&lt;td&gt;📄 Standard&lt;/td&gt;
&lt;td&gt;📝 Detailed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type Inference&lt;/td&gt;
&lt;td&gt;🧠 Powerful&lt;/td&gt;
&lt;td&gt;📚 Standard&lt;/td&gt;
&lt;td&gt;🧠 Powerful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maturity&lt;/td&gt;
&lt;td&gt;🆕 Beta&lt;/td&gt;
&lt;td&gt;✅ Stable&lt;/td&gt;
&lt;td&gt;✅ Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnostics&lt;/td&gt;
&lt;td&gt;🔍 Multi-file context&lt;/td&gt;
&lt;td&gt;📊 Single-file&lt;/td&gt;
&lt;td&gt;🔍 Cross-file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language Server&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;td&gt;⚠️ Via dmypy&lt;/td&gt;
&lt;td&gt;✅ Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Current Limitations
&lt;/h2&gt;

&lt;p&gt;As of v0.0.9 (Beta):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited rule set&lt;/strong&gt;: Main rules implemented, more coming in 2026&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No strict mode yet&lt;/strong&gt;: Won't complain about missing type annotations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beta status&lt;/strong&gt;: API may change before stable release&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, even in beta, ty is already catching real bugs and providing value in production codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with ty
&lt;/h2&gt;

&lt;p&gt;Here's a practical workflow for adopting ty in your project:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install and Run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;ty@latest
ty check src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Review Results
&lt;/h3&gt;

&lt;p&gt;ty will show you actual bugs in your code, even without type annotations!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Add to CI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add to your test script&lt;/span&gt;
tox &lt;span class="nt"&gt;-e&lt;/span&gt; ty

&lt;span class="c"&gt;# Or run directly&lt;/span&gt;
ty check src tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Gradual Type Annotation
&lt;/h3&gt;

&lt;p&gt;As you maintain your code, gradually add type annotations where it makes sense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With annotations, ty becomes even more powerful!&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: FastAPI Application
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;connect_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
        &lt;span class="c1"&gt;# Bug: FastAPI expects HTTPException, but we're returning None!
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users/{user_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Bug: user could be None, but we're accessing .dict()
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ty will catch both bugs:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;get_user&lt;/code&gt; can return &lt;code&gt;None&lt;/code&gt;, but FastAPI expects an exception or a valid user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user.dict()&lt;/code&gt; could fail if user is &lt;code&gt;None&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Performance Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use file exclusion&lt;/strong&gt;: Skip generated files and vendor directories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable caching&lt;/strong&gt;: ty caches results for unchanged files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt;: ty automatically uses multiple cores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental mode&lt;/strong&gt;: In your editor, only changed files are re-checked&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;According to Astral, ty is targeting a stable 1.0 release in 2026, with plans for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ More comprehensive rule coverage&lt;/li&gt;
&lt;li&gt;✅ Strict mode (enforce type annotations)&lt;/li&gt;
&lt;li&gt;✅ Better integration with popular frameworks&lt;/li&gt;
&lt;li&gt;✅ Plugin system for custom rules&lt;/li&gt;
&lt;li&gt;✅ Performance optimizations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;ty represents the next evolution in Python type checking. Its combination of blazing speed, powerful inference, and excellent diagnostics makes it a compelling choice for Python developers.&lt;/p&gt;

&lt;p&gt;While still in beta, ty is already proving valuable for catching real bugs and improving code quality. If you're using mypy or Pyright and frustrated by slow check times, ty is worth trying.&lt;/p&gt;

&lt;p&gt;The Astral team has an excellent track record with Ruff and uv, and ty looks set to be another game-changer in the Python tooling ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Official Website: &lt;a href="https://astral.sh/ty" rel="noopener noreferrer"&gt;astral.sh/ty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 Documentation: &lt;a href="https://docs.astral.sh/ty/" rel="noopener noreferrer"&gt;docs.astral.sh/ty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/astral-sh/ty" rel="noopener noreferrer"&gt;github.com/astral-sh/ty&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 Discord: &lt;a href="https://astral.sh/discord" rel="noopener noreferrer"&gt;Astral Discord&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Have you tried ty yet? What's your experience with Python type checkers? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>python</category>
      <category>typechecker</category>
      <category>tooling</category>
      <category>performance</category>
    </item>
    <item>
      <title>Introducing azurecost: Azure Cost Management Made Simple</title>
      <dc:creator>Hiroshi Toyama</dc:creator>
      <pubDate>Tue, 16 Dec 2025 12:35:15 +0000</pubDate>
      <link>https://forem.com/toyama0919/introducing-azurecost-azure-cost-management-made-simple-1o3k</link>
      <guid>https://forem.com/toyama0919/introducing-azurecost-azure-cost-management-made-simple-1o3k</guid>
      <description>&lt;p&gt;If you've ever struggled to understand your Azure costs through the portal's cluttered interface, you're not alone. That's why I created &lt;code&gt;azurecost&lt;/code&gt; - a straightforward command-line tool that gives you instant visibility into your Azure spending.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Azure's Cost Management portal is powerful, but sometimes you just want quick answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How much did I spend last month?"&lt;/li&gt;
&lt;li&gt;"Which services are costing me the most?"&lt;/li&gt;
&lt;li&gt;"What's the cost breakdown by resource group?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Navigating through the Azure portal for these simple questions feels like overkill. You need something faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azurecost&lt;/code&gt; is a Python CLI tool that brings Azure cost data directly to your terminal. No clicking through menus, no waiting for the portal to load - just type a command and get your answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple by default&lt;/strong&gt;: Just run &lt;code&gt;azurecost&lt;/code&gt; and see your monthly costs by service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible dimensions&lt;/strong&gt;: Group costs by resource group, service name, location, or any combination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple time ranges&lt;/strong&gt;: View daily or monthly costs for any time period&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-currency detection&lt;/strong&gt;: Displays costs in your subscription's billing currency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python API&lt;/strong&gt;: Use it programmatically in your scripts and automation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Installation is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you're logged in with Azure CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;JPY&lt;span class="o"&gt;)&lt;/span&gt;                 2023-08    2023-09
&lt;span class="nt"&gt;------------------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;  &lt;span class="nt"&gt;---------&lt;/span&gt;
total                  492.77      80.28
Cognitive Services     492.77      80.28
Bandwidth                0          0
Storage                  0          0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, readable, instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-dimensional Analysis
&lt;/h3&gt;

&lt;p&gt;Want to see costs broken down by both resource group and service? Easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription &lt;span class="nt"&gt;-d&lt;/span&gt; ResourceGroup &lt;span class="nt"&gt;-d&lt;/span&gt; ServiceName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Daily Monitoring
&lt;/h3&gt;

&lt;p&gt;Tracking costs during a migration or deployment? Switch to daily granularity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription &lt;span class="nt"&gt;-g&lt;/span&gt; DAILY &lt;span class="nt"&gt;-a&lt;/span&gt; 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows you the last 7 days of costs, broken down by day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focused Investigation
&lt;/h3&gt;

&lt;p&gt;Need to check costs for a specific resource group?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; my-subscription &lt;span class="nt"&gt;-r&lt;/span&gt; production-rg &lt;span class="nt"&gt;-a&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows 3 months of costs for just that resource group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;The idea for &lt;code&gt;azurecost&lt;/code&gt; came from a recurring frustration in my daily work as a cloud engineer. Every morning, I wanted to do a quick cost check - nothing fancy, just a sanity check to make sure nothing was unexpectedly spiking. But this simple task meant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opening a browser&lt;/li&gt;
&lt;li&gt;Navigating to the Azure portal (and waiting for it to load)&lt;/li&gt;
&lt;li&gt;Finding the right subscription&lt;/li&gt;
&lt;li&gt;Clicking through to Cost Management&lt;/li&gt;
&lt;li&gt;Waiting for the cost analysis page to render&lt;/li&gt;
&lt;li&gt;Configuring the time range and dimensions I wanted&lt;/li&gt;
&lt;li&gt;Waiting again for the data to load&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the time I got my answer, 2-3 minutes had passed. Multiply that by every time someone on the team needed to check costs, and we're talking about hours of wasted time per week.&lt;/p&gt;

&lt;p&gt;I realized I was doing the same exact query every time. I didn't need the portal's full power - I needed something as simple as running &lt;code&gt;git status&lt;/code&gt; or &lt;code&gt;docker ps&lt;/code&gt;. Something that gave me the information I needed in 2 seconds, not 2 minutes.&lt;/p&gt;

&lt;p&gt;Then there were the times when I was SSH'd into a server or working in a restricted environment where opening a browser wasn't convenient. Or when I wanted to pipe cost data into a script for automated reporting. The portal simply wasn't designed for these workflows.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;azurecost&lt;/code&gt;. The goal was simple: make checking Azure costs feel as natural as any other command-line operation. No ceremony, no waiting, no clicking.&lt;/p&gt;

&lt;p&gt;What started as a personal productivity hack turned into something my team adopted, then colleagues at other companies started using it, and eventually it made sense to release it as an open-source tool.&lt;/p&gt;

&lt;p&gt;The philosophy behind &lt;code&gt;azurecost&lt;/code&gt; is that most of the time, you don't need 100 different options and visualizations. You need a fast, reliable answer to a simple question. The tool does one thing well: show you your Azure costs quickly and clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using It in Your Scripts
&lt;/h2&gt;

&lt;p&gt;The Python API makes it easy to integrate cost monitoring into your automation:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Azurecost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;granularity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DAILY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServiceName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ResourceGroup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;subscription_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-subscription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ago&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert_tabulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily cost reports sent to Slack&lt;/li&gt;
&lt;li&gt;Budget alerts in your CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Cost attribution in team dashboards&lt;/li&gt;
&lt;li&gt;Custom reporting for finance teams&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Tips
&lt;/h2&gt;

&lt;p&gt;Set environment variables to avoid typing the same options repeatedly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AZURE_RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production-rg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now just run &lt;code&gt;azurecost&lt;/code&gt; with no arguments. It'll use your configured subscription and resource group automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The tool is actively maintained and open to contributions. Some ideas I'm exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost anomaly detection&lt;/li&gt;
&lt;li&gt;Budget tracking and alerts&lt;/li&gt;
&lt;li&gt;Cost forecasting based on historical trends&lt;/li&gt;
&lt;li&gt;Integration with other cloud providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;The tool is open source and available on PyPI. Give it a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;azurecost
azurecost &lt;span class="nt"&gt;-s&lt;/span&gt; your-subscription
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for full documentation, examples, and to report issues or contribute.&lt;/p&gt;

&lt;p&gt;Azure cost management doesn't have to be complicated. Sometimes the best tool is the simplest one.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;🔗 GitHub: &lt;a href="https://github.com/toyama0919/azurecost" rel="noopener noreferrer"&gt;toyama0919/azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 PyPI: &lt;a href="https://pypi.org/project/azurecost/" rel="noopener noreferrer"&gt;azurecost&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐍 Python: 3.8+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have questions or feedback? Open an issue on GitHub or drop a comment below!&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cli</category>
      <category>python</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
