<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Kaxil]]></title><description><![CDATA[Senior Director of Engineering at Astronomer, Apache Airflow Committer & PMC member since 2018!. Writing about AI-native engineering & open source at Fortune 500 scale!]]></description><link>https://kaxil.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!HOIl!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab319124-ecf7-43dc-82de-441f0a8f0f5e_1484x1484.jpeg</url><title>Kaxil</title><link>https://kaxil.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 07 Apr 2026 16:30:47 GMT</lastBuildDate><atom:link href="https://kaxil.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Kaxil]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[kaxil@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[kaxil@substack.com]]></itunes:email><itunes:name><![CDATA[Kaxil]]></itunes:name></itunes:owner><itunes:author><![CDATA[Kaxil]]></itunes:author><googleplay:owner><![CDATA[kaxil@substack.com]]></googleplay:owner><googleplay:email><![CDATA[kaxil@substack.com]]></googleplay:email><googleplay:author><![CDATA[Kaxil]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA["MCP Sucks" (Until It Doesn't)]]></title><description><![CDATA[Garry Tan called MCP "sucks," then changed his mind two months later. Here's when CLI, server-side MCP, and REST APIs each actually win.]]></description><link>https://kaxil.substack.com/p/mcp-vs-cli-vs-rest</link><guid isPermaLink="false">https://kaxil.substack.com/p/mcp-vs-cli-vs-rest</guid><dc:creator><![CDATA[Kaxil]]></dc:creator><pubDate>Tue, 07 Apr 2026 00:01:01 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/50d13be9-59cb-416a-901d-05df87688e5d_2400x1260.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The &#8220;MCP is dead&#8221; discourse peaked in earlier this year. <a href="https://ejholmes.github.io/2026/02/28/mcp-is-dead-long-live-the-cli.html">Eric Holmes&#8217; post</a> hit #1 on Hacker News. <a href="https://x.com/garrytan/status/2031910564344262988">Garry Tan called it &#8220;sucks&#8221;</a>. <a href="https://nevo.systems/blogs/news/perplexity-drops-mcp-protocol-72-percent-context-window-waste">Perplexity&#8217;s CTO dropped it internally</a>, saying it was &#8220;too expensive for production.&#8221; </p><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://x.com/garrytan/status/2031910564344262988&quot;,&quot;full_text&quot;:&quot;MCP sucks honestly\n\nIt eats too much context window and you have to toggle it on and off and the auth sucks\n\nI got sick of Claude in Chrome via MCP and vibe coded a CLI wrapper for Playwright tonight in 30 minutes only for my team to tell me Vercel already did it lmao\n\nBut it&quot;,&quot;username&quot;:&quot;garrytan&quot;,&quot;name&quot;:&quot;Garry Tan&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1922894268403941377/-dGWAt3N_normal.jpg&quot;,&quot;date&quot;:&quot;2026-03-12T01:50:04.000Z&quot;,&quot;photos&quot;:[],&quot;quoted_tweet&quot;:{&quot;full_text&quot;:&quot;The cofounder and CTO of Perplexity, @denisyarats just said internally at Perplexity they&#8217;re moving away from MCPs and instead using APIs and CLIs &#128064;&quot;,&quot;username&quot;:&quot;morganlinton&quot;,&quot;name&quot;:&quot;Morgan&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1906488198186098688/5zfTtZz8_normal.jpg&quot;},&quot;reply_count&quot;:433,&quot;retweet_count&quot;:208,&quot;like_count&quot;:3834,&quot;impression_count&quot;:1261747,&quot;expanded_url&quot;:null,&quot;video_url&quot;:null,&quot;belowTheFold&quot;:false}" data-component-name="Twitter2ToDOM"></div><p>Then, two months later, <a href="https://x.com/garrytan/status/2040516449127764058">Garry Tan changed his mind</a>: </p><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://x.com/garrytan/status/2040516449127764058&quot;,&quot;full_text&quot;:&quot;I changed my mind. MCP can be wonderful. \n\nIt just needs to be light and purpose-built and engineered instead of a shitty shim over your existing REST API&quot;,&quot;username&quot;:&quot;garrytan&quot;,&quot;name&quot;:&quot;Garry Tan&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1922894268403941377/-dGWAt3N_normal.jpg&quot;,&quot;date&quot;:&quot;2026-04-04T19:46:47.000Z&quot;,&quot;photos&quot;:[],&quot;quoted_tweet&quot;:{&quot;full_text&quot;:&quot;@garrytan MCP was a very costly mistake, but useful as an adoption vector (for now). Should have repurposed gRPC. We could have saved 6-24 months.&quot;,&quot;username&quot;:&quot;BraaiEngineer&quot;,&quot;name&quot;:&quot;braai engineer&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1988576931944574976/mOYU_67L_normal.jpg&quot;},&quot;reply_count&quot;:30,&quot;retweet_count&quot;:7,&quot;like_count&quot;:159,&quot;impression_count&quot;:37576,&quot;expanded_url&quot;:null,&quot;video_url&quot;:null,&quot;belowTheFold&quot;:false}" data-component-name="Twitter2ToDOM"></div><p>The MCP critics correctly identified the problem (bloated, auto-converted MCP servers) but in my opinion they incorrectly blamed the protocol! I&#8217;ve shipped an MCP server, a CLI, and 30+ agent skills. Here&#8217;s when each approach actually wins.</p><h2>CLI Wins When You Have a Terminal</h2><p>On pure performance, CLI + Skills beats MCP on every measured dimension for developers and agents with terminal access.</p><p>I use <code>gh</code> CLI for everything GitHub-related, not the GitHub MCP server. Claude Code defaults to <code>gh</code> too. </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:null}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># CLI: 1 tool call, shell filters the result, ~1,400 tokens
gh issue list --limit 5 --json title,state | jq '.[] | .title'

# MCP: model loads 93 tool schemas (~55K tokens), picks one,
# gets the full response in context, then reasons over it</code></pre></div><p><a href="https://www.scalekit.com/blog/mcp-vs-cli-use">ScaleKit benchmarked</a> this across 75 runs using Claude Sonnet 4. CLI used 4&#8211;32x fewer tokens per operation. CLI hit 100% success rate; MCP hit 72%. At 10,000 operations per month, CLI costs ~$3.20 vs MCP&#8217;s ~$55.20.</p><p>The pipe architecture is why. Intermediate JSON never enters the context window. The shell does the filtering, not the model. <code>jq</code>, <code>grep</code>, <code>awk</code>, <code>head</code> are context-window-efficient filters with no MCP equivalent.</p><p>The context window tax compounds. GitHub&#8217;s MCP server exposed 93 tools at the time of that benchmark, roughly 55,000 tokens of schemas before the first message. (They&#8217;ve since <a href="https://github.com/github/github-mcp-server/discussions/1182">cut the defaults to 52</a>, but it&#8217;s still a lot.) Stack a few MCP servers and you can lose 72% of a 200K context window to schemas alone.</p><p>Every major coding agent (Claude Code, Codex, Cursor, Aider, OpenHands) uses bash as its foundation because models trained on billions of bash examples. Zero MCP schemas in training data. Even Anthropic conceded: their <a href="https://www.anthropic.com/engineering/code-execution-with-mcp">code execution approach</a> reduces 150K tokens down to 2K, a 98.7% reduction.</p><h3>The Context Window Complaint Is Outdated</h3><p>A lot of the "MCP eats my context window" complaints haven't caught up with the fixes. <a href="https://code.claude.com/docs/en/mcp#scale-with-mcp-tool-search">Claude Code</a> now defers MCP tool schemas by default and loads them on demand. <a href="https://cursor.com/blog/dynamic-context-discovery">Cursor</a> does the same, reporting a 46.9% reduction in total agent tokens. <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool">Anthropic's API</a>, <a href="https://developers.openai.com/api/docs/guides/tools-tool-search">OpenAI</a>, and <a href="https://blog.cloudflare.com/code-mode/">Cloudflare</a> have shipped similar approaches. If you're using Claude Code or Cursor, you already have tool search. If you're building on the raw API, you need to opt in (<code>defer_loading: true</code> per tool). The ScaleKit benchmarks above don't mention tool search and tested against GitHub's hosted MCP server directly, so they likely reflect the pre-tool-search experience. The gap is narrowing fast for coding agents with tool search enabled, but the full context tax still hits API builders and Claude Desktop users who haven't opted in.</p><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://x.com/simonw/status/2011570719856214153&quot;,&quot;full_text&quot;:&quot;This is great - context pollution is why I rarely used MCP, now that it's solved there's no reason not to hook up dozens or even hundreds of MCPs to Claude Code&quot;,&quot;username&quot;:&quot;simonw&quot;,&quot;name&quot;:&quot;Simon Willison&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/378800000261649705/be9cc55e64014e6d7663c50d7cb9fc75_normal.jpeg&quot;,&quot;date&quot;:&quot;2026-01-14T22:46:47.000Z&quot;,&quot;photos&quot;:[],&quot;quoted_tweet&quot;:{&quot;full_text&quot;:&quot;&quot;,&quot;username&quot;:&quot;trq212&quot;,&quot;name&quot;:&quot;Thariq&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1976939058741039104/r3GgzqRh_normal.jpg&quot;},&quot;reply_count&quot;:71,&quot;retweet_count&quot;:161,&quot;like_count&quot;:3082,&quot;impression_count&quot;:459868,&quot;expanded_url&quot;:null,&quot;video_url&quot;:null,&quot;belowTheFold&quot;:true}" data-component-name="Twitter2ToDOM"></div><p>CLI has a cost most people don&#8217;t talk about, though: you&#8217;re giving the agent a shell, and the security surface is the entire operating system.</p><h2>But Why Not Just APIs?</h2><p>MCP&#8217;s auth IS OAuth 2.1, the same standard REST uses. OpenAPI has described APIs for machines since 2015. You can auto-generate MCP servers from OpenAPI specs. Every agent can call REST APIs with <code>requests</code> or <code>curl</code>. No special client libraries needed. MCP adds JSON-RPC, session management, and SSE complexity on top of plain HTTP.</p><p>All true. So if MCP generation from OpenAPI is mechanical, why not let each client auto-generate what they need? Because auto-generated servers are what got us into the 93-tool, 55K-token mess. Curation requires human judgment about what an agent needs vs. what an API exposes.</p><p>Several SaaS companies with excellent REST APIs (Linear, Stripe, Slack, Figma, GitHub, Notion, Sentry) built hand-curated MCP servers anyway. REST can do the job technically. What it doesn&#8217;t give you: frictionless distribution, non-developer access, and centralized control over what each agent can see.</p><p>With a REST API, every consumer gets the same endpoints. With an MCP server, you can scope tools per token. An on-call bot gets <code>get_dag_runs</code> and <code>get_task_logs</code>. A CI agent gets <code>trigger_dag_run</code>. An analyst gets read-only tools. Same server, different tool sets based on who&#8217;s connecting.</p><p>REST APIs also carry versioning overhead: v1/v2, deprecation cycles, migration guides, client library updates. MCP tools describe themselves at runtime. The model reads the schema dynamically, so the server can evolve without the same breaking-change coordination.</p><h2>What Neither Solves</h2><p>&#8220;Just use CLI + an API backend with auth and logging.&#8221; Sure, you could. But now you also need per-token tool scoping and a schema agents can discover. You&#8217;ve just rebuilt MCP, except yours doesn&#8217;t plug into Claude Desktop, Cursor, or ChatGPT without custom integration.</p><h3>Distribution</h3><p>CLI means downloading a binary, installing dependencies, managing PATH, dealing with platform differences, and handling version skew. REST is better but each consumer still writes their own integration code: auth, endpoint mapping, response parsing. <a href="http://agentskills.io/">Agent Skills</a> have the same problem. Skill paths vary across skill.sh, Claude Code, and Cursor, and the <a href="https://github.com/modelcontextprotocol/experimental-ext-skills">MCP community is exploring</a> using MCP itself as the distribution channel for skills. (More on Skills &amp; its distribution in a separate post soon.)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://kaxil.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://kaxil.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>MCP: URL + token. Done. The server updates once and every MCP-compatible client (Claude Desktop, Cursor, ChatGPT, Codex, and hundreds more) gets the new version instantly. No binary downloads, no deps, no path resolution, no upgrades on the consumer side. The integration math shifts from M&#215;N (every consumer &#215; every API) to M+N (one server + any number of clients). Anthropic&#8217;s <a href="https://platform.claude.com/docs/en/agents-and-tools/mcp-connector">MCP Connector</a> takes this further: API-to-API, no client code at all.</p><p>It&#8217;s also convenient from Claude Code, Cursor etc to know when a MCP server is down or needs authentication.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5DjX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5DjX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 424w, https://substackcdn.com/image/fetch/$s_!5DjX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 848w, https://substackcdn.com/image/fetch/$s_!5DjX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 1272w, https://substackcdn.com/image/fetch/$s_!5DjX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5DjX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png" width="882" height="556" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:556,&quot;width&quot;:882,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:79507,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kaxil.substack.com/i/193369008?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5DjX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 424w, https://substackcdn.com/image/fetch/$s_!5DjX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 848w, https://substackcdn.com/image/fetch/$s_!5DjX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 1272w, https://substackcdn.com/image/fetch/$s_!5DjX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b7ad636-5850-486e-8ca1-af59dda61419_882x556.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>MCP has a rough edge here, though: try connecting to two Slack workspaces or two Snowflake accounts from the same client. Both server instances expose identically-named tools (<code>send_message</code>, <code>query</code>), and most clients (<a href="https://github.com/anthropics/claude-code/issues/32549">Claude Code</a>, <a href="https://forum.cursor.com/t/support-for-multiple-instances-of-the-same-mcp/109034">Cursor</a>) route calls to the wrong instance or suppress one entirely. The <a href="https://modelcontextprotocol.io/specification/draft/server/tools">spec says clients should prefix tool names</a> to disambiguate, but nobody does it well yet. CLI tools handle multi-instance naturally.</p><h3>Non-Terminal Consumers and Non-Developers</h3><p>Some consumers don&#8217;t have a terminal at all. A large marketplace company runs an on-call bot that queries their data platform for incident triage. Read-only, no human in the loop, no terminal. It needs HTTP transport with authentication. Claude Desktop users want to check their pipeline runs without installing a CLI. Mobile apps and internal AI products need server-side access. Server-side MCP doesn&#8217;t care about machine paths or local config. It works the same whether the consumer is a desktop app, a mobile client, or another service.</p><p>Then there are the people who interact with data platforms but aren&#8217;t developers. An analyst checking if a pipeline ran, a PM tracking data freshness, an SRE who touches the system once a month and doesn&#8217;t want to install a CLI and learn its flags. Server-side MCP gives them access through tools they already use (Claude Desktop, ChatGPT, internal chatbots) without asking them to open a terminal.</p><p>Skills need a local environment. They reference files on disk, run scripts relative to a working directory, and assume a terminal. Not an option for a Slack bot, a mobile app, or a non-technical user.</p><h3>Access Control and Security</h3><p>Any system with coarse role models has this problem: the API&#8217;s permissions don&#8217;t match what you&#8217;d give an agent. You end up with roles that are either too restrictive (can read but can&#8217;t trigger operations) or too permissive (can trigger operations but also read secrets and credentials).</p><p>CLI-based agents add another dimension to this. You&#8217;re giving the agent a shell, and the security surface is the entire operating system. When <a href="https://alex000kim.com/posts/2026-03-31-claude-code-source-leak/">Anthropic&#8217;s Claude Code source leaked via npm</a>, <a href="https://adversa.ai/blog/claude-code-security-bypass-deny-rules-disabled/">security researchers at Adversa found</a> that deny rules silently stopped working when a shell command contained more than 50 subcommands. A developer who configured &#8220;never run <code>rm</code>&#8220; would see it blocked normally, but 50 no-op commands before the dangerous one bypassed the check entirely. Anthropic patched it, but the broader pattern is that CLI permission models are hard to get right because the set of possible commands is unbounded.</p><p>MCP servers have their own security risks. Tool responses are untrusted text that the model interprets, and a compromised server can embed instructions that hijack the agent&#8217;s behavior (prompt injection). Neither approach is inherently safe. The difference is the attack surface: CLI agents can do anything the shell allows; MCP servers expose a fixed set of operations.</p><p>The fix for the role-model problem is a filtering proxy: expose the safe operational endpoints, hide the ones where secrets live. For enterprises, this is where MCP earns its keep. The security boundary lives on your infrastructure instead of on every developer&#8217;s laptop. Scoped tokens per team or role. Audit logs showing which agent called which tool and when. You can update access policies without pushing changes to every client. Enterprises already have API gateways (Kong, Apigee) for REST, and those work too. MCP&#8217;s advantage is combining the gateway function with distribution and non-terminal consumer access in one layer, instead of running three separate systems.</p><h2>Which One When</h2><p><a href="https://justin.poehnelt.com/posts/mcp-abstraction-tax/">Justin Poehnelt calls it the abstraction tax</a>: every layer between the agent&#8217;s intent and the API loses fidelity. The question is whether the accessibility gain justifies the loss. His framing: &#8220;MCP and CLIs aren&#8217;t competing. They&#8217;re different answers to the same question: how much fidelity are you willing to trade for accessibility?&#8221;</p><p>For simple API surfaces, 5&#8211;8 curated tools cover the main use cases without severe fidelity loss. For complex APIs with hundreds of endpoints, the tax is much steeper and CLI or direct REST makes more sense.</p><p>MCP the protocol is fine. MCP servers built by auto-converting 80 REST endpoints into 80 MCP tools are what poison the context window and confuse the model. Garry Tan landed on this too: &#8220;light and purpose-built and engineered.&#8221;</p><p>Here&#8217;s how I think about it:</p><ul><li><p><strong>CLI + Skills</strong> for developers and agents with terminal access. 4&#8211;32x fewer tokens, pipe architecture, bash is already in the model weights.</p></li><li><p><strong>Server-side MCP</strong> for everyone who doesn&#8217;t have a terminal or doesn&#8217;t want one: non-developers (analysts, PMs, SREs), non-terminal agents and bots, mobile apps, and enterprises that need a central control plane with scoped tokens and audit logs.</p></li></ul><p>These are layers, not competitors. Skills teach agents how to think about a domain. CLI and server-side MCP are two ways to give agents tool access, optimized for different consumers.</p><p><a href="https://www.ado.im/posts/claude-customization-stack-mcp-skills-plugins/">Aaron Ott</a> put it well: MCP is the transport, skills are the instructions. The future is probably both flowing through the same pipe.</p><div><hr></div><p>Build for both. CLI + Skills where you have terminal access, server-side MCP where you don&#8217;t. And whatever you do, don&#8217;t auto-convert your entire API into MCP. Curate ruthlessly.</p><p><em>Next up: why Skills are the most powerful layer in this stack, and how to build them without the distribution headaches. Subscribe if you want the details.</em></p>]]></content:encoded></item><item><title><![CDATA[AI Agents Don't Read Changelogs: Why Your Package Manager Needs a Cooldown]]></title><description><![CDATA[A week ago, someone backdoored litellm on PyPI. The attacker poisoned a Trivy GitHub Action in litellm&#8217;s CI/CD pipeline, stole the PyPI publish token, and pushed two malicious versions (1.82.7 and 1.82.8). The payload was a .pth file that runs on every Python process startup, it harvested credentials, tried to move laterally across Kubernetes clusters, and installed a persistent systemd backdoor.]]></description><link>https://kaxil.substack.com/p/ai-agents-package-manager-cooldown</link><guid isPermaLink="false">https://kaxil.substack.com/p/ai-agents-package-manager-cooldown</guid><dc:creator><![CDATA[Kaxil]]></dc:creator><pubDate>Tue, 31 Mar 2026 10:12:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9738ef75-269d-4f53-bde6-f82db602d4b4_1200x675.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A week ago, someone backdoored <a href="https://www.sonatype.com/blog/compromised-litellm-pypi-package-delivers-multi-stage-credential-stealer">litellm on PyPI</a>. The attacker poisoned a Trivy GitHub Action in litellm&#8217;s CI/CD pipeline, stole the PyPI publish token, and pushed two malicious versions (1.82.7 and 1.82.8). The payload was a <code>.pth</code> file that runs on every Python process startup, it harvested credentials, tried to move laterally across Kubernetes clusters, and installed a persistent systemd backdoor. litellm sits in 36% of cloud environments. The malicious versions were up for about three hours.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kaxil.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Today, it happened again. Shortly after midnight UTC on March 31, someone with access to the axios npm account published <code>axios@1.14.1</code>. It looked normal. It resolved as latest. It contained a remote access trojan.</p><p>The malicious version injected a fake dependency called <code>plain-crypto-js</code> with a postinstall script that downloaded platform-specific RAT payloads: a binary on macOS, a PowerShell script on Windows, a Python dropper on Linux. It phoned home to a C2 server, set up persistence, then deleted its own traces and swapped i<code>s package.json</code> with a clean decoy.</p><p>Socket flagged the malicious dependency within six minutes, but the compromised axios versions stayed live for about three hours before npm pulled them. Anyone who ran `npm install` with an unpinned axios dependency during that window got owned.</p><p>This keeps happening. <a href="https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident">event-stream</a>. <a href="https://github.com/faisalman/ua-parser-js/issues/536">ua-parser-js</a>. <a href="https://www.bleepingcomputer.com/news/security/dev-corrupts-npm-libs-colors-and-faker-breaking-thousands-of-apps/">colors and faker</a>. Now <code>litellm</code> and <code>axios</code> in the same week. The playbook is the same every time: compromise a maintainer account (or CI pipeline), publish a malicious version, race against detection. The window is small, usually hours, but the blast radius is huge because package managers resolve to latest by default, immediately.</p><p>One of the fix: don&#8217;t install packages published less than N days ago.</p><h2>Native support</h2><h3><strong>npm (v11.10+)</strong></h3><pre><code><code># ~/.npmrc
min-release-age=7</code></code></pre><p>One line. npm won&#8217;t resolve any version published less than 7 days ago. Covers <code>npm install</code>, <code>npm add</code>, and <code>npx</code>. Value is in days.</p><h3><strong>pnpm (v10.16+)</strong></h3><pre><code><code># pnpm-workspace.yaml
minimumReleaseAge: 10080  # minutes (= 7 days)</code></code></pre><p>Same behavior. Value is in minutes.</p><h3><strong>Bun (v1.3+)</strong></h3><pre><code><code># ~/.bunfig.toml
[install]
minimumReleaseAge = 604800</code></code></pre><p>Value is in seconds. 604800 = 7 days.</p><h3><strong>uv (v0.9.17+)</strong></h3><pre><code><code># ~/.config/uv/uv.toml
exclude-newer = "7 days"</code></code></pre><p>Accepts relative durations (<code>7 days</code>, <code>1 week</code>, <code>P7D</code>) and absolute timestamps. Covers <code>uv pip install</code>, <code>uv add</code>, <code>uv sync</code>, and <code>uv run --with</code>.</p><h3><strong>pip (v26.0+)</strong></h3><pre><code><code>pip install --uploaded-prior-to 2026-03-24 requests</code></code></pre><p>Absolute timestamps only, no relative durations. If you use uv for Python, its config already covers this.</p><h3><strong>Yarn (v4.10+)</strong></h3><pre><code><code># .yarnrc.yml
npmMinimalAgeGate: 10080  # minutes (= 7 days)</code></code></pre><p>Yarn Classic (v1) has no support. Use npm or pnpm instead.</p><h3><strong>Deno (v2.6+)</strong></h3><pre><code><code>deno update --minimum-dependency-age P7D</code></code></pre><p>Flag-based, applies to <code>deno update</code> and <code>deno outdated</code>.</p><h2><strong>Cargo, Go, and Gem</strong></h2><p>No native support yet.</p><ul><li><p>Cargo has no native cooldown support. The third-party <code>cargo-cooldown</code> wrapper exists. Pin exact versions and review before updating.</p></li><li><p>Go modules are immutable once fetched by <code>proxy.golang.org</code>, which prevents after-the-fact tampering. But there&#8217;s no publish-age filter. Pin exact versions in <code>go.mod</code>.</p></li><li><p>Gem has <code>gem.coop</code> enforcing a 48-hour delay, but <code>rubygems.org</code> has no equivalent. Pin in <code>Gemfile.lock</code>.</p></li></ul><h2><strong>The AI agent angle</strong></h2><p>Coding agents (Claude Code, Cursor, Copilot) make this worse. A human might notice something odd about an install. An agent won&#8217;t &#8212; it&#8217;ll resolve to latest, run postinstall scripts, and keep going.</p><p>I added a Claude Code <a href="https://docs.anthropic.com/en/docs/claude-code/hooks">PreToolUse hook</a> that does two things:</p><ol><li><p><strong>Blocks package managers without native cooldown</strong> (pip, cargo, go, gem) and suggests safe alternatives</p></li><li><p><strong>Blocks attempts to bypass native configs</strong> (passing <code>--min-release-age=0</code>, <code>--exclude-newer</code>, editing <code>.npmrc</code>, etc.)</p></li></ol><p>When the agent tries <code>pip install some-package</code>, it gets:</p><pre><code><code>BLOCKED by package quarantine: pip &#8212; no native date-gate

Policy: all packages must be published 7+ days ago to protect against
supply-chain attacks (compromised maintainer accounts publishing malicious
versions of legitimate packages).

Use 'uv pip install' instead (protected by exclude-newer="7 days"
in ~/.config/uv/uv.toml).

To override: ask the user to either install it manually from their
terminal, or prefix the command with QUARANTINE_BYPASS=1.</code></code></pre><p>Safe patterns pass through: <code>pip install -r requirements.txt</code>, <code>npm ci</code>, bare <code>pnpm install</code> (lockfile-only). The hook only applies to agent tool calls; your terminal is unrestricted.</p><p>The hook is a <a href="https://gist.github.com/kaxil/8427deeefcefd8f682c7b176dd345929">bash script</a> wired up in <code>settings.json</code>:</p><pre><code><code>{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/skills/package-quarantine/scripts/quarantine-hook.sh"
          }
        ]
      }
    ]
  }
}
</code></code></pre><p>Package managers with native protection (npm, pnpm, bun, uv) don&#8217;t need hook enforcement, they already enforce cooldown before resolving.</p><h2><strong>5-minute setup</strong></h2><p>If you only use JavaScript and Python:</p><pre><code><code># npm
echo "min-release-age=7" &gt;&gt; ~/.npmrc

# pnpm (add to pnpm-workspace.yaml)
# minimumReleaseAge: 10080

# bun
mkdir -p ~/.config &amp;&amp; cat &gt;&gt; ~/.bunfig.toml &lt;&lt; 'EOF'
[install]
minimumReleaseAge = 604800
EOF

# uv (Python)
mkdir -p ~/.config/uv &amp;&amp; cat &gt;&gt; ~/.config/uv/uv.toml &lt;&lt; 'EOF'
exclude-newer = "7 days"
EOF</code></code></pre><p>Static files. No cron jobs, no shell profile changes. Lockfile workflows (<code>npm ci</code>, <code>pnpm install</code>, <code>bun install</code> with no arguments) still work &#8212; they don&#8217;t resolve new versions.</p><h2><strong>The tradeoff</strong></h2><p>You can&#8217;t install a package published today. When you need something new, a security patch, a dependency for a fresh project, you override:</p><pre><code><code># npm: one-time override
npm install --min-release-age=0 new-package@1.0.0

# uv: one-time override (use current timestamp)
uv pip install --exclude-newer="$(date -u +%Y-%m-%dT%H:%M:%SZ)" new-package</code></code></pre><p>That&#8217;s the right default. You make a conscious choice to trust a specific new release. Everything else waits.</p><p>Most supply-chain attacks are caught within 24-72 hours. A 7-day window handles everything except long-game attacks where malicious code sits dormant for weeks. Those need different defenses (dependency auditing, SBOMs, provenance verification).</p><h2><strong>What this doesn&#8217;t cover</strong></h2><ul><li><p>Malicious code in packages older than 7 days</p></li><li><p>Registry infrastructure compromises</p></li><li><p>Git dependencies or direct URL installs</p></li><li><p>Typosquats that survive more than 7 days undetected</p></li></ul><p>This is one layer. Pair it with pnpm&#8217;s <code>strictDepBuilds</code> and lockfile review. But cooldown gives you the most coveragPe for the least effort.</p><div><hr></div><p><em>Axios attack details: <a href="https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan">StepSecurity</a>, <a href="https://socket.dev/blog/axios-npm-package-compromised">Socket</a>. LiteLLM attack details: <a href="https://www.sonatype.com/blog/compromised-litellm-pypi-package-delivers-multi-stage-credential-stealer">Sonatype</a>, <a href="https://www.trendmicro.com/en_us/research/26/c/inside-litellm-supply-chain-compromise.html">Trend Micro</a>. </em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kaxil.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[I Haven't Written a Line of Code in 4 Months (But I Ship More Than Ever)]]></title><description><![CDATA[I&#8217;m a Senior Director of Engineering at Astronomer and a committer and PMC member of the Apache Airflow project &#8212; 2+ million lines of code, ~45k GitHub stars, thousands of contributors, used by most Fortune 500 companies.]]></description><link>https://kaxil.substack.com/p/i-havent-written-a-line-of-code-in</link><guid isPermaLink="false">https://kaxil.substack.com/p/i-havent-written-a-line-of-code-in</guid><dc:creator><![CDATA[Kaxil]]></dc:creator><pubDate>Sun, 22 Mar 2026 04:02:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a021d477-7b8a-4c32-84e6-138885d31090_2400x1260.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m a Senior Director of Engineering at <a href="https://www.astronomer.io/">Astronomer</a> and a committer and PMC member of the <a href="https://github.com/apache/airflow">Apache Airflow</a> project &#8212; 2+ million lines of code, ~45k GitHub stars, thousands of contributors, used by most Fortune 500 companies.</p><p>I haven&#8217;t written a single line of code by hand in four months. But I&#8217;d still call it my best 6 months ever and I took six weeks of paternity leave in the middle of it.</p><p>This didn&#8217;t happen overnight. I&#8217;ve been coding through AI agents for a couple of years now. I started by writing Cursor rules &amp; Slash commands for our open source Airflow team at Astronomer and sharing them across the engineering org so everyone could use the same patterns. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ppVU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ppVU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 424w, https://substackcdn.com/image/fetch/$s_!ppVU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 848w, https://substackcdn.com/image/fetch/$s_!ppVU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 1272w, https://substackcdn.com/image/fetch/$s_!ppVU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ppVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png" width="1422" height="888" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:888,&quot;width&quot;:1422,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:229510,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://kaxil.substack.com/i/191706000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ppVU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 424w, https://substackcdn.com/image/fetch/$s_!ppVU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 848w, https://substackcdn.com/image/fetch/$s_!ppVU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 1272w, https://substackcdn.com/image/fetch/$s_!ppVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64447d01-4e03-41e5-ba9b-1d7a8fb6d066_1422x888.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dkT_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dkT_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 424w, https://substackcdn.com/image/fetch/$s_!dkT_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 848w, https://substackcdn.com/image/fetch/$s_!dkT_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!dkT_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dkT_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png" width="1456" height="1042" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1042,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:305178,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kaxil.substack.com/i/191706000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dkT_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 424w, https://substackcdn.com/image/fetch/$s_!dkT_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 848w, https://substackcdn.com/image/fetch/$s_!dkT_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 1272w, https://substackcdn.com/image/fetch/$s_!dkT_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab523cb7-6e1b-4e85-b852-d6f5225a2fd0_2088x1494.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>When Anthropic launched Agent skills in December 2025, I moved to those since they were a clear superset and a standard. I have spent hundreds of hours since then iterating on skills, hooks, CLIs and MCP + integrations building the tooling that lets my agents work the way I would. Four months ago I stopped writing code by hand altogether.</p><h2>What Actually Shipped</h2><p>Here's some of what I shipped in last few months, all through AI coding agents:</p><p><strong><a href="https://airflow.apache.org/registry/">Apache Airflow Registry</a></strong>: I built an official, community-governed catalog for all of the Airflow integrations. Drove internal feedback, authored the proposal, built the tooling, backfilled 1.5 years of data for all providers. The registry includes versioned APIs exposing every Operator, Hook, and parameter designed for AI agents to consume.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U9Jk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U9Jk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 424w, https://substackcdn.com/image/fetch/$s_!U9Jk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 848w, https://substackcdn.com/image/fetch/$s_!U9Jk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 1272w, https://substackcdn.com/image/fetch/$s_!U9Jk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U9Jk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png" width="1046" height="1236" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1236,&quot;width&quot;:1046,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:469927,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kaxil.substack.com/i/191706000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U9Jk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 424w, https://substackcdn.com/image/fetch/$s_!U9Jk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 848w, https://substackcdn.com/image/fetch/$s_!U9Jk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 1272w, https://substackcdn.com/image/fetch/$s_!U9Jk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cc223e0-64b7-4c44-9eb6-7bba9524733a_1046x1236.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong><a href="https://github.com/astronomer/agents">Astronomer Agents repo</a></strong>: open-source agent skills platform for Data Engineers across the world. 30+ PRs: multi-database support across Snowflake, BigQuery, Postgres, and Redshift. An agent-first CLI with direct Airflow REST API access and a MCP server for Airflow.  Warehouse schema discovery delivering 3.5x faster queries.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!07gA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!07gA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 424w, https://substackcdn.com/image/fetch/$s_!07gA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 848w, https://substackcdn.com/image/fetch/$s_!07gA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 1272w, https://substackcdn.com/image/fetch/$s_!07gA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!07gA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png" width="1044" height="902" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:902,&quot;width&quot;:1044,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:431479,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kaxil.substack.com/i/191706000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!07gA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 424w, https://substackcdn.com/image/fetch/$s_!07gA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 848w, https://substackcdn.com/image/fetch/$s_!07gA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 1272w, https://substackcdn.com/image/fetch/$s_!07gA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5fa8094-be12-4f46-91e3-aab3515dca8d_1044x902.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>150+ PRs</strong> across <a href="https://github.com/apache/airflow">apache/airflow</a>, <a href="https://github.com/apache/airflow-site">airflow-site</a>, <a href="https://github.com/astronomer/agents">Astronomer Agents repo</a>, and various internal repos. Fixed N+1 queries in the scheduler, eliminated eager loading, bulk <code>DELETE</code> for XCom cleanup. </p><p><strong>Airflow website modernization</strong>: <a href="https://github.com/apache/airflow-site/pull/1270">the ancient docsy theme</a> (6 years old!) had no dark mode, broken syntax highlighting, and poor search. I implemented full dark mode, fixed code blocks, added <a href="https://github.com/apache/airflow/pull/59658">Cmd+K search using Pagefind</a>, updated archived docs. This is the first thing every new Airflow user sees, so this was on my list for some time and I was incredibly happy to finally do it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gqdH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gqdH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 424w, https://substackcdn.com/image/fetch/$s_!gqdH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 848w, https://substackcdn.com/image/fetch/$s_!gqdH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 1272w, https://substackcdn.com/image/fetch/$s_!gqdH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gqdH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png" width="1456" height="837" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:837,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="image" title="image" srcset="https://substackcdn.com/image/fetch/$s_!gqdH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 424w, https://substackcdn.com/image/fetch/$s_!gqdH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 848w, https://substackcdn.com/image/fetch/$s_!gqdH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 1272w, https://substackcdn.com/image/fetch/$s_!gqdH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b756310-58e5-441b-8333-1f5ec570eee8_2882x1656.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong><a href="https://github.com/apache/airflow/pulls?q=is%3Apr+project%3Aapache%2F586+is%3Aclosed+author%3Akaxil">AIP-99</a></strong>: making Airflow the first AI-native orchestrator with my friend Pavan. Existing Airflow hooks for GCS, S3, Snowflake, and others become AI agent tools via Pydantic AI. No other production-grade orchestrator has native LLM/agent primitives at this level.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A5nG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A5nG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 424w, https://substackcdn.com/image/fetch/$s_!A5nG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 848w, https://substackcdn.com/image/fetch/$s_!A5nG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!A5nG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A5nG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png" width="1044" height="1388" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1388,&quot;width&quot;:1044,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:705235,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://kaxil.substack.com/i/191706000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!A5nG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 424w, https://substackcdn.com/image/fetch/$s_!A5nG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 848w, https://substackcdn.com/image/fetch/$s_!A5nG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 1272w, https://substackcdn.com/image/fetch/$s_!A5nG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81f7f8f4-229a-4d90-beaf-436dfd487956_1044x1388.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="twitter-embed" data-attrs="{&quot;url&quot;:&quot;https://x.com/kaxil/status/2029537997566128161?s=20&quot;,&quot;full_text&quot;:&quot;&#128226;&#128227; Coming soon to a <span class=\&quot;tweet-fake-link\&quot;>@ApacheAirflow</span>  DAG near you!\n\nApache Airflow 3 is getting native agentic AI tools. 350+ provider hooks pre-authenticated, any LLM, one decorator.\n\nYour existing Airflow connections become AI agent tools.\n\n&#129525;&#128071; &quot;,&quot;username&quot;:&quot;kaxil&quot;,&quot;name&quot;:&quot;Kaxil Naik&quot;,&quot;profile_image_url&quot;:&quot;https://pbs.substack.com/profile_images/1295115007676612609/SwgPMbgD_normal.jpg&quot;,&quot;date&quot;:&quot;2026-03-05T12:42:20.000Z&quot;,&quot;photos&quot;:[{&quot;img_url&quot;:&quot;https://pbs.substack.com/media/HCpdOh8WQAAPIgV.jpg&quot;,&quot;link_url&quot;:&quot;https://t.co/2AaRYSC5YX&quot;}],&quot;quoted_tweet&quot;:{},&quot;reply_count&quot;:4,&quot;retweet_count&quot;:10,&quot;like_count&quot;:74,&quot;impression_count&quot;:3997,&quot;expanded_url&quot;:null,&quot;video_url&quot;:null,&quot;belowTheFold&quot;:true}" data-component-name="Twitter2ToDOM"></div><p><strong>An Impact Analysis Agent </strong>that helps ensure breaking changes don&#8217;t make it to Airflow releases. This has been critical in our usage at Astronomer, details on this <a href="https://www.astronomer.io/blog/the-ai-agent-behind-astro-runtimes-reliability/">Engineering blog post</a>.</p><p>Half of these I wouldn't have even started without agents: not enough hours in the day alongside everything else and especially wouldn&#8217;t have spent any time on frontend projects :)</p><h2>What a Day Actually Looks Like</h2><p>Here&#8217;s a real Friday &#8212; March 20, 2026:</p><ul><li><p>27 Claude Code sessions across 6 repositories</p></li><li><p>Created an incident triage skill from a live P1 customer incident</p></li><li><p>Updated my slack skill to do better searches</p></li><li><p>Ported a CLI command from Python to Go</p></li><li><p>Fixed a registry UI bug &#8212; found it, investigated, PR merged same hour</p></li><li><p>Reviewed 15 PRs across apache/airflow and internal repos</p></li><li><p>Caught a docs PR where a find-and-replace had corrupted 20 files</p></li><li><p>Prepared slides for upcoming presentation</p></li><li><p>Reviewed and authored Google Docs using workspace skills</p></li><li><p>50+ Slack messages across 12 channels</p></li></ul><p>Not a single line typed into an editor.</p><p>I&#8217;m not &#8220;telling AI to write code.&#8221; I&#8217;m directing agents across repos, tools, and communication channels. All day.</p><h2>How It Actually Works</h2><p>People hear &#8220;I don&#8217;t write code&#8221; and assume I&#8217;m prompting Claude or Cursor. That&#8217;s not what&#8217;s happening.</p><p><strong>Skills are the new programming language.</strong> A skill is a markdown file that tells an agent what to do, what tools to use, and what patterns to follow. I have skills for PR review, incident triage, meeting notes, daily summaries, and blog writing. Each one encodes my judgment &#8212; &#8220;review like I would review.&#8221; Writing a good skill requires the same thinking as writing good code. The skill IS the code.</p><p><strong>My stack:</strong></p><ul><li><p><strong>Claude Code</strong>: I don&#8217;t open a code editor anymore. I do use Cursor at times, but only for using Agents, not for writing code.</p></li><li><p><strong>Skills</strong>: markdown instructions, composable. Contains tons of references &amp; scripts called from main <code>SKILL.md</code> file.</p></li><li><p><strong>Hooks</strong>: guardrails. Block posts containing AI slop words. Require confirmation before publishing.</p></li><li><p><strong>MCP servers &amp; CLIs</strong>: GitHub, Slack, Google Calendar, Gmail, Google Docs, Todoist. Agents read and write to real systems.</p></li><li><p><strong>Subagents</strong>: For things requiring context isolation and/or restricted permissions</p></li><li><p><strong><a href="https://code.claude.com/docs/en/agent-teams">Agent teams</a></strong>: multiple agents working in parallel across different parts of the codebase for larger tasks.</p></li><li><p><strong>Personal CRM</strong>: an Obsidian vault where agents write meeting notes, daily journals, and cross-link people.</p></li></ul><p>My typical workflow for Airflow as an example now is: I describe a feature, the agent  writes it, spins up our dev environment (Breeze), runs the full E2E test suite including UI interactions, and takes screenshots and screen recordings for me to verify or use in a demo. </p><p>My agents read my email, search Slack, check my calendar, manage my todos, write meeting notes, generate daily journals, and post PR reviews. People keep asking me how I set this up while safeguarding against any prompt injections. That&#8217;s partly why I have been thinking of blogging again after years.</p><p>The job hasn&#8217;t gotten easier. It&#8217;s shifted. Less typing, more thinking. Less execution, more direction.</p><h2>What&#8217;s hard</h2><p>I could write only the wins. But if I&#8217;m going to be honest about this, here&#8217;s what&#8217;s actually hard.</p><p><strong>I prototype faster than I can get buy-in.</strong> I can have a working prototype in hours. Getting five stakeholders to agree on scope takes weeks. Agents made that gap worse. </p><p><strong>Code review is now the bottleneck.</strong> Code generation is nearly free. Review cannot scale the same way. A Google engineer told me he has five features sitting in a queue waiting for review. AI-generated PRs are flooding into Airflow&#8217;s open source repo &#8212; varying quality, same review cost per line. If I review one line, I own it for the lifecycle of the project. That hasn&#8217;t changed.</p><p><strong>Agents fail silently.</strong> A wrong line of code throws an error. A wrong agent output looks plausible. I&#8217;ve caught agents confidently applying the wrong fix, generating tests that pass but test nothing, and making changes that look right until you read the diff carefully. The failure mode isn&#8217;t &#8220;it broke&#8221; &#8212; it&#8217;s &#8220;it looked fine and I almost shipped it.&#8221; This is why you should review the code before creating PRs &#8212; especially if it is going to be deployed in Production systems! Accountability hasn&#8217;t changed. AI is a tool, &#8220;<em><strong>you are still responsible for the code you write&#8221;</strong></em>.</p><p><strong>Breadth vs. depth gets harder.</strong> In 4.5 effective months I was context-switching between Airflow 3.1 release management, the provider registry, impact analysis agent, a production incident investigation, dependency firefighting, a hackathon, proposal design, the agents repo, upstream PRs, and various other things. Agents let you touch everything. The discipline is knowing when to stop and go deep instead. This remains a challenge.S</p><h2>What I&#8217;ve Learned</h2><p><strong>The harness matters more than the model.</strong> Models haven&#8217;t improved dramatically in the last 3 months at least. The last step change was Claude Opus 4.5. What changed is the tooling around them &#8212; skills, context injection, hooks, guardrails. A Google engineer said this in a joint session we did: &#8220;Models haven&#8217;t improved much. Harnesses are what changed.&#8221; If you&#8217;re investing in AI coding, invest in the harness.</p><p><strong>Skills are composable</strong>. A review skill can call a search skill can call a Slack skill. This is the new &#8220;functions calling functions.&#8221; Your workflow IS your IP. The patterns encoded in your skills are very valuable. I fork every skill I write and every skill I use. I also use ton of reference file and almost always have hooks &amp; scripts where deterministic output is needed.</p><p><strong>Route by complexity, review across models.</strong> Agents overcomplicate architecture if you let a heavy model loose on a simple task. Haiku for expense tracking and simple reminders, Sonnet for everyday coding, Opus for deep architectural work. I also get better results by reviewing with a different model than the one that wrote the code &#8212; write with Claude, review with GPT, or vice versa. </p><p><strong>Turn incidents into lasting defenses.</strong> A production incident took 3 days to diagnose and led to a 15,000x query speedup fix. Instead of closing the ticket and moving on, I added the failure pattern to an Impact Analysis agent&#8217;s knowledge base. The system now catches that class of issue automatically. Every incident should leave behind an agent that prevents the next one.</p><p></p><h2>What &#8220;Learn to Code&#8221; Means Now</h2><p>I&#8217;m not saying it&#8217;s dead. I&#8217;m saying it means something different than it did two years ago:</p><ul><li><p>Learn to write clear instructions. That&#8217;s what <strong>skills</strong> are.</p></li><li><p>Learn to set guardrails. That&#8217;s what <strong>hooks</strong> are.</p></li><li><p>Learn to review code you didn&#8217;t write. This was always important. Now it&#8217;s the whole job.</p></li><li><p>Learn to debug agent failures. This is a new skill entirely.</p></li><li><p>Learn to say &#8220;that&#8217;s wrong&#8221; to confident-sounding output.</p></li></ul><p>The engineers who thrive will be the ones who can direct agents with the same precision they once applied to writing code. The ones who struggle will be the ones waiting for someone to tell them what to type.</p><p>I still call myself an engineer. I just don&#8217;t type the code anymore.</p><p><em>Next up: I&#8217;ll open-source some of the skill files I use daily and share how I use OpenClaw to run AI agents for my family &amp; my personal use on WhatsApp. Subscribe if you want to see what this looks like from the inside.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kaxil.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item></channel></rss>