<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hector Flores</title>
    <description>The latest articles on DEV Community by Hector Flores (@htekdev).</description>
    <link>https://dev.to/htekdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2155191%2F4eb16de9-82ac-4486-b7cd-6c0ec2b33daf.png</url>
      <title>DEV Community: Hector Flores</title>
      <link>https://dev.to/htekdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/htekdev"/>
    <language>en</language>
    <item>
      <title>My Sister Shipped 5 PRs in an Hour</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 30 Jun 2026 14:55:35 +0000</pubDate>
      <link>https://dev.to/htekdev/my-sister-shipped-5-prs-in-an-hour-4gap</link>
      <guid>https://dev.to/htekdev/my-sister-shipped-5-prs-in-an-hour-4gap</guid>
      <description>&lt;p&gt;My sister Sofia is a marketing graduate. She works the front desk at our dad's diesel mechanic shop, answers calls, books appointments, hands customers their keys. She had never opened a pull request. She did not know what a pull request &lt;em&gt;was&lt;/em&gt;. She had never heard the words "worktree", "migration", or "preview deployment".&lt;/p&gt;

&lt;p&gt;Last week she shipped five PRs in one hour. By the end of the night she had shipped around twenty. Each one was a real change against a real codebase, gated by &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; CI, isolated in its own &lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;git worktree&lt;/a&gt;, previewed on its own &lt;a href="https://vercel.com/docs/deployments/preview-deployments" rel="noopener noreferrer"&gt;Vercel deployment&lt;/a&gt;, merged through an approval gate she controlled from her phone. She never saw a terminal. She talked to a &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Telegram&lt;/a&gt; bot she named "Daniella" — apologized to it when she changed her mind, thanked it when it finished — and the harness underneath did the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is not a no-code story.&lt;/strong&gt; Sofia is a non-technical product owner shipping real production changes. The thesis of this article is exactly one sentence: &lt;em&gt;converting a vibe coder into a production workflow engineer is enabled, almost entirely, by platform harness engineering.&lt;/em&gt; The model is not the lever. The prompt is not the lever. The harness is the lever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Sofia Actually Did
&lt;/h2&gt;

&lt;p&gt;Sofia opened Telegram. She typed what she wanted. A few minutes later she got a URL. She clicked it, looked at her app in a real browser tab, and replied "I like it" or "add this" or "change the color". When she was happy, she tapped an approve button. The change went live. She repeated that loop twenty times.&lt;/p&gt;

&lt;p&gt;That is the entire user-facing surface. There is no IDE. No &lt;code&gt;git&lt;/code&gt; command. No staged hunks. No conflict resolution. No CI dashboard. There is a chat window, a preview URL, and an approval button. That surface is what most people call "vibe coding". On its own, it produces toys. Wired to a real harness, it produces shipped software.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Sofia Never Saw
&lt;/h2&gt;

&lt;p&gt;Every one of those twenty PRs went through machinery she has never heard of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fresh &lt;a href="https://htek.dev/articles/git-worktree-unlocks-agentic-development/" rel="noopener noreferrer"&gt;git worktree&lt;/a&gt; was spun up per change so each agent run was isolated from every other in-flight change.&lt;/li&gt;
&lt;li&gt;The agent edited code in that worktree, ran any required data migrations, and verified its own work against an automated test suite before opening a PR.&lt;/li&gt;
&lt;li&gt;The PR triggered a &lt;a href="https://vercel.com/docs/deployments/preview-deployments" rel="noopener noreferrer"&gt;Vercel preview deployment&lt;/a&gt; with its own URL and its own ephemeral database — the link she clicked in Telegram.&lt;/li&gt;
&lt;li&gt;CI gates ran on every PR: lint, type checks, unit tests, build. A failing gate means the approve button never lights up.&lt;/li&gt;
&lt;li&gt;Her "approve" tap was an &lt;em&gt;intent signal&lt;/em&gt;, not a merge. It told a separate &lt;strong&gt;agent-merge&lt;/strong&gt; tool: "Sofia wants this in." That agent then took ownership of the actual git work — rebase against the latest main, wait for CI to finish, resolve trivially mergeable conflicts, escalate the ones that aren't, and merge PRs sequentially in a safe order. The full pattern is in &lt;a href="https://htek.dev/articles/agent-merge-speed-without-losing-control/" rel="noopener noreferrer"&gt;Agent Merge: Speed Without Losing Control&lt;/a&gt; — the single biggest platform improvement that came out of Sofia's night.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sofia's mental model is: I asked, I checked, I approved. That model is honest. The harness is what makes it true.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulf210s8uk9hpr2w06zj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fulf210s8uk9hpr2w06zj.webp" alt="Two-layer diagram: Sofia's chat-and-approve surface above; the invisible harness (worktrees, CI gates, Vercel previews, HookFlow merge) below." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sofia operates on the top layer. The bottom layer — six pieces of platform engineering — is what makes the top layer honest.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Merge Conflict That Forced a Better Architecture
&lt;/h2&gt;

&lt;p&gt;Around PR number six, things broke. She had six, seven, eight open PRs at once — all touching adjacent surfaces, all racing each other. Classic merge-conflict pile-up. A normal developer would notice, rebase, resolve, push. Sofia had no concept of any of those verbs. She just kept tapping approve.&lt;/p&gt;

&lt;p&gt;That night forced the single biggest platform improvement I have shipped this year: I built &lt;strong&gt;agent-merge&lt;/strong&gt;, a tool that splits "intent of approval" from "execution of merge". The human signals intent — one tap, "ship it" — and a separate governed agent picks up approved PRs and does the actual git work: rebase against the latest main, wait for CI to finish, resolve trivially mergeable conflicts, escalate the ones that aren't, and merge everything sequentially in a deterministic order. Sofia kept tapping approve. Agent-merge made sure each approval landed cleanly, one at a time, no pile-up.&lt;/p&gt;

&lt;p&gt;I wrote the long version in &lt;a href="https://htek.dev/articles/agent-merge-speed-without-losing-control/" rel="noopener noreferrer"&gt;Agent Merge: Speed Without Losing Control&lt;/a&gt;. The supporting mechanics are in &lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;Stop Trusting AI Agents with Git — Start Governing Them&lt;/a&gt; and &lt;a href="https://htek.dev/articles/aspect-oriented-programming-ai-agents-hookflows/" rel="noopener noreferrer"&gt;Aspect-Oriented Programming for AI Agents&lt;/a&gt;. The short version: the bottleneck stopped being "the agent making mistakes" and started being "the harness not catching them yet". Agent-merge was the harness that caught the merge-conflict pile-up before it caught Sofia.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffag5bn5aip90cfacxhq6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffag5bn5aip90cfacxhq6.webp" alt="Approval intent vs merge execution diagram: human signals " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The split that became agent-merge: Sofia signals intent with a tap; the merge-agent handles rebase, CI waits, conflict resolution, and sequential merging.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Daniella
&lt;/h2&gt;

&lt;p&gt;Sofia named the bot Daniella. I don't name my bots. I never have. To me they are tools — important tools, but tools. When the agent finishes a task I close the loop and move on. When Sofia's agent finished a task she typed "thank you, Daniella". When she changed her mind mid-feature she typed "sorry, Daniella, can we go back".&lt;/p&gt;

&lt;p&gt;That difference is not trivia. It is a real signal about how non-technical users relate to agents. Developers project tool-ness onto an agent. We see the API, we see the loop, we see the prompt template. Non-developers project &lt;em&gt;colleague-ness&lt;/em&gt;. They form a working relationship with the thing on the other side of the chat. That changes everything about how the agent should communicate — error messages, status updates, refusal patterns, even pacing. The colleague metaphor is more accurate to what is actually happening underneath, and we developers may be the ones with the wrong mental model.&lt;/p&gt;

&lt;p&gt;It also means a non-developer's tolerance for ungoverned agent behavior is &lt;em&gt;much lower&lt;/em&gt; than ours. They take "I'll do that" literally. They don't second-guess. When Daniella says it's done, Sofia believes it is done. So Daniella had better not be wrong. The only way to make sure she isn't is the harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Harness Is the Developer Multiplier
&lt;/h2&gt;

&lt;p&gt;Here is the part the industry keeps getting backwards. The discourse is overwhelmingly about the &lt;em&gt;model&lt;/em&gt; (which is bigger, which is smarter, which writes cleaner functions) and the &lt;em&gt;prompt&lt;/em&gt; (how to phrase, what to include, which framework to use). Both matter at the margin. Neither is the lever.&lt;/p&gt;

&lt;p&gt;The lever is the harness around the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://htek.dev/articles/agent-harnesses-controlling-ai-agents-2026/" rel="noopener noreferrer"&gt;agent harness&lt;/a&gt; that defines what the agent can and cannot touch.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://htek.dev/articles/agent-hooks-controlling-ai-codebase/" rel="noopener noreferrer"&gt;hooks&lt;/a&gt; that enforce deterministic checks before non-deterministic action.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://htek.dev/articles/agent-merge-speed-without-losing-control/" rel="noopener noreferrer"&gt;agent-merge tool&lt;/a&gt; that turns an approval tap into a safe, sequential, CI-gated merge — the piece of harness that came directly from Sofia's night.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt; that gives the agent the right slice of the world at the right time.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://htek.dev/articles/how-to-build-governed-ai-agent-systems/" rel="noopener noreferrer"&gt;governance frameworks&lt;/a&gt; — approval gates, scoped permissions, audit trails — that make the system safe enough to hand to someone who has never run &lt;code&gt;git status&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://htek.dev/articles/copilot-cli-telegram-bridge-mobile-ai-terminal/" rel="noopener noreferrer"&gt;Telegram-to-CLI bridge&lt;/a&gt; that puts the whole stack into Sofia's pocket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote &lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;What Is Harness as Code&lt;/a&gt; to give this discipline a name. Sofia's night is the clearest proof I have that the name matters. Every additional PR she pushed, I was on the platform side adding another HookFlow, tightening another scope, codifying another piece of context. I was not coaching her. I was not reviewing her code. I was engineering the harness underneath her in real time, and the harness was what scaled.&lt;/p&gt;

&lt;p&gt;Pull the harness away and Sofia ships nothing. Hand the same harness to a senior engineer and you get a developer who is suddenly 5–10x faster, because the same machinery that absorbs Sofia's inexperience absorbs &lt;em&gt;their&lt;/em&gt; context-switching, fatigue, and 2 AM mistakes. The harness is not a beginner's tool. It is everyone's tool. Sofia is the proof that it works at the floor; senior teams are where it pays the rent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowqtwxzdm8zwzu8wnly0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowqtwxzdm8zwzu8wnly0.webp" alt="Side-by-side comparison: Sofia ships ~20 PRs/evening with the harness and 0 without; a senior engineer goes from 1× to 5–10× throughput with the same harness." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same lever, two starting points. The harness raises the floor and the ceiling at the same time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skill of the Next Decade
&lt;/h2&gt;

&lt;p&gt;If you lead a platform team, a DevOps function, or an SRE org, this is the part to internalize. The skill that decides who wins the next decade of software is not "knows how to prompt". It is &lt;strong&gt;harness engineering&lt;/strong&gt;: HookFlows, deterministic gates around non-deterministic agents, scoped contexts, audit-friendly approval flows, isolation primitives like worktrees and ephemeral environments. This is real engineering work, with real testability, real observability, real on-call implications. It is also where almost all of the leverage in agentic development lives.&lt;/p&gt;

&lt;p&gt;I've argued versions of this before — in &lt;a href="https://htek.dev/articles/three-pillars-agentic-devops-zero-to-hero/" rel="noopener noreferrer"&gt;Three Pillars of Agentic DevOps&lt;/a&gt;, in &lt;a href="https://htek.dev/articles/agentic-development-maturity-curve/" rel="noopener noreferrer"&gt;The Agentic Development Maturity Curve&lt;/a&gt;, in &lt;a href="https://htek.dev/articles/your-god-prompt-is-the-new-monolith/" rel="noopener noreferrer"&gt;Your God Prompt Is the New Monolith&lt;/a&gt;. Sofia's night is the cleanest data point I have. A marketing grad with no programming background, on her phone, shipped twenty production PRs in an evening because someone had spent months engineering the harness around the agent. Without that harness she ships zero. With it, she out-ships most senior developers I have worked with on raw PR count, with comparable per-PR quality.&lt;/p&gt;

&lt;p&gt;If you take one thing from this article, take the sentence I keep going back to in my own head: &lt;strong&gt;converting a vibe coder into a production workflow engineer is enabled almost entirely by platform harness engineering.&lt;/strong&gt; Stop optimizing prompts. Stop chasing the next model. Start engineering harnesses. That is where the next class of developers — Sofia, and everyone like her — is going to come from. And it is where your existing developers are going to find the next order of magnitude.&lt;/p&gt;

&lt;p&gt;I am very proud of my sister. I am even more sure, after watching her work, that the discipline I have been building toward is the right one. The harness is the lever. Build the lever.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/agent-merge-speed-without-losing-control/" rel="noopener noreferrer"&gt;Agent Merge: Speed Without Losing Control&lt;/a&gt; — &lt;strong&gt;the merge-agent tool born from Sofia's night; the single biggest platform improvement that came out of this story&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;Stop Trusting AI Agents with Git — Start Governing Them&lt;/a&gt; — supporting merge-governance mechanics&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/aspect-oriented-programming-ai-agents-hookflows/" rel="noopener noreferrer"&gt;Aspect-Oriented Programming for AI Agents (HookFlows)&lt;/a&gt; — the underlying mechanic&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;What Is Harness as Code&lt;/a&gt; — naming the discipline&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/agent-harnesses-controlling-ai-agents-2026/" rel="noopener noreferrer"&gt;Agent Harnesses: Controlling AI Agents in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/agent-hooks-controlling-ai-codebase/" rel="noopener noreferrer"&gt;Agent Hooks: Controlling Your AI Codebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/git-worktree-unlocks-agentic-development/" rel="noopener noreferrer"&gt;Git Worktree Unlocks Agentic Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/copilot-cli-telegram-bridge-mobile-ai-terminal/" rel="noopener noreferrer"&gt;The Copilot CLI + Telegram Bridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/how-to-build-governed-ai-agent-systems/" rel="noopener noreferrer"&gt;How to Build Governed AI Agent Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;What Is Context Engineering — A Practical Guide From 50+ Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/three-pillars-agentic-devops-zero-to-hero/" rel="noopener noreferrer"&gt;Three Pillars of Agentic DevOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/agentic-development-maturity-curve/" rel="noopener noreferrer"&gt;The Agentic Development Maturity Curve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/your-god-prompt-is-the-new-monolith/" rel="noopener noreferrer"&gt;Your God Prompt Is the New Monolith&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;git-worktree documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/docs/deployments/preview-deployments" rel="noopener noreferrer"&gt;Vercel Preview Deployments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Telegram Bot API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agenticdevelopment</category>
      <category>platformengineering</category>
      <category>github</category>
      <category>devex</category>
    </item>
    <item>
      <title>13 PRs. One Day. No Manual Merges.</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 30 Jun 2026 14:55:26 +0000</pubDate>
      <link>https://dev.to/htekdev/13-prs-one-day-no-manual-merges-251c</link>
      <guid>https://dev.to/htekdev/13-prs-one-day-no-manual-merges-251c</guid>
      <description>&lt;p&gt;Iteration 1 looked like this: my agent opened a PR, immediately called &lt;code&gt;gh pr merge --squash --delete-branch&lt;/code&gt; in PowerShell, and merged itself into &lt;code&gt;main&lt;/code&gt;. Raw merge access, no guardrails. The Vercel deployment broke. I spent 40 minutes fixing it.&lt;/p&gt;

&lt;p&gt;Iteration 5 looked like this: Sofia got a Telegram notification — a new feature PR for the shop management app — with three buttons: &lt;strong&gt;✅ Merge Now&lt;/strong&gt;, &lt;strong&gt;🤖 Agent Merge&lt;/strong&gt;, &lt;strong&gt;❌ Deny&lt;/strong&gt;. She tapped 🤖 Agent Merge and went back to what she was doing. The merge-agent rebased the PR onto &lt;code&gt;main&lt;/code&gt;, waited 3 minutes for &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; and the Vercel preview deployment to go green, and squash-merged it. Zero diff read by a human. The right outcome shipped.&lt;/p&gt;

&lt;p&gt;The gap between those two states is a five-iteration story about building the merge layer that agentic development actually requires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Merge Layer Is the Bottleneck
&lt;/h2&gt;

&lt;p&gt;When you have 10 agent-authored PRs queued and every merge requires a developer to checkout the branch, read the diff, run tests locally, and click approve on GitHub, you've recreated the bottleneck you were trying to break. The merge layer — not the code generation layer — is the throughput ceiling for agentic teams. Fix it with deterministic tooling and approval that doesn't require humans to read code.&lt;/p&gt;

&lt;p&gt;Most teams focus their agentic investment on faster code generation. The real leverage is in what happens &lt;em&gt;after&lt;/em&gt; the code is written. An agent that ships a PR 3x faster than a developer means nothing if that PR waits two days in review. Fix the merge layer first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five Iterations to One System
&lt;/h2&gt;

&lt;p&gt;Each iteration of this system fixed one specific failure mode from the previous version. Iteration 1 had agents merging directly. Iteration 5 has a 3-button Telegram approval, a durable local ledger, and a patient merge-agent that handles rebases and CI waits automatically. The path between them is the lesson.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 1 — Naive Auto-Merge
&lt;/h3&gt;

&lt;p&gt;Agents had raw PowerShell access and could call &lt;code&gt;gh pr merge&lt;/code&gt; directly. The failure mode wasn't that agents were malicious — it was that they couldn't distinguish "CI hasn't run yet" from "CI is green." The first naive merge broke the &lt;code&gt;main&lt;/code&gt; branch of &lt;code&gt;htekdev/htek-dev-site&lt;/code&gt; within 72 hours. &lt;strong&gt;Lesson:&lt;/strong&gt; agents need a deterministic merge tool, not raw shell access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 2 — Deterministic Merge Tool
&lt;/h3&gt;

&lt;p&gt;I introduced &lt;code&gt;dev_merge_pr&lt;/code&gt; — a tool that checks PR state (open, non-draft, no merge conflicts), verifies all required &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-required-status-checks" rel="noopener noreferrer"&gt;GitHub Actions status checks&lt;/a&gt; are passing, then calls the &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits" rel="noopener noreferrer"&gt;GitHub squash merge API&lt;/a&gt;. Agents call &lt;code&gt;dev_merge_pr&lt;/code&gt;; raw &lt;code&gt;gh pr merge&lt;/code&gt; is blocked. This was safer but the approval model was still the GitHub web UI — Hector reads diff, clicks approve, waits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; the merge tool needs an approval mechanism that's physically closer to the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 3 — Hookflow Lockdown
&lt;/h3&gt;

&lt;p&gt;I added &lt;code&gt;enforce-merge-pr-tool-only.yml&lt;/code&gt;, a &lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;hookflow that fires on every PowerShell tool call&lt;/a&gt; and blocks any command matching &lt;code&gt;gh pr merge&lt;/code&gt;, direct GitHub API merge endpoints (&lt;code&gt;/pulls/{N}/merge&lt;/code&gt;), and &lt;code&gt;gh api&lt;/code&gt; merge combos. Agents literally cannot route around the approval requirement — there's no shell escape hatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; safety without throughput is just a different kind of frustration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 4 — The Conflict Wall
&lt;/h3&gt;

&lt;p&gt;Sofia started operating AI agents as a non-technical user — describing features, approving previews, driving parallel workstreams without writing code. She'd launch 5–6 agent sessions, each producing a PR using &lt;a href="https://htek.dev/articles/git-worktree-unlocks-agentic-development/" rel="noopener noreferrer"&gt;isolated git worktrees&lt;/a&gt;. Then the PRs conflicted with each other. PR #1 merges, PR #2 is now behind &lt;code&gt;main&lt;/code&gt; and needs a rebase. PR #3 is behind and conflicting. I spent 20 minutes per session on manual rebases.&lt;/p&gt;

&lt;p&gt;The deeper problem: &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches" rel="noopener noreferrer"&gt;GitHub dismisses PR approvals when new commits are pushed&lt;/a&gt; — specifically when "Dismiss stale pull request approvals when new commits are pushed" is enabled in branch protection. Every rebase wiped the approval and triggered another review cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; tie approvals to the PR, not the commit SHA.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iteration 5 — The Current System
&lt;/h3&gt;

&lt;p&gt;When an agent finishes a PR, it calls &lt;code&gt;merge_pr(repo, pr_number, description)&lt;/code&gt; via the telegram-bridge extension. This triggers a &lt;a href="https://core.telegram.org/bots/features#inline-keyboards" rel="noopener noreferrer"&gt;Telegram inline keyboard message&lt;/a&gt; to the configured approver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;htekdev/taller-mecanico#47 — "Add appointment sorting by urgency"

✅ Merge Now  |  🤖 Agent Merge  |  ❌ Deny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Merge Now&lt;/strong&gt; triggers an immediate squash merge if CI is already green. &lt;strong&gt;Agent Merge&lt;/strong&gt; records the approval in &lt;code&gt;data/merge-queue.json&lt;/code&gt; and dispatches the merge-agent. &lt;strong&gt;Deny&lt;/strong&gt; ends the request. The approval takes 3 seconds. The rest is automated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmxb6h8a3puola7ndwxhm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmxb6h8a3puola7ndwxhm.webp" alt="Diagram of the three-button Telegram approval flow: an agent calls merge_pr(), Telegram presents Merge Now, Agent Merge, and Deny buttons, and each branch shows the steps the system takes after a human taps once." width="799" height="386"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Iteration 5: one tap routes the PR into immediate merge, queued merge-agent processing, or denial — the human spends three seconds, the system handles the rest.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Approval Ledger That Survives Rebases
&lt;/h2&gt;

&lt;p&gt;A local JSON ledger keyed on PR number — not commit SHA — is the design decision that makes everything else work. The merge-agent can rebase a branch three times and push three new commits, and Sofia's approval still holds. She approved the &lt;em&gt;intent&lt;/em&gt; (this PR should ship), not a specific diff state. No external API, no approval state to reset, no rate limits.&lt;/p&gt;

&lt;p&gt;When Sofia taps &lt;strong&gt;🤖 Agent Merge&lt;/strong&gt;, the system writes an entry to &lt;code&gt;data/merge-queue.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;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"htekdev/taller-mecanico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pr_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sofia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-22T14:31:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sha_at_approval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approved"&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;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiwdnng7589si4agkuisj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiwdnng7589si4agkuisj.webp" alt="Side-by-side comparison: GitHub's native review ties approval to a commit SHA and gets dismissed on every rebase, while the local merge-queue.json ledger ties approval to a PR number and survives any number of rebases until CI is green." width="800" height="397"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;GitHub dismisses approvals on every rebase because they're bound to a SHA. The local ledger binds approval to a PR number — intent, not bytes — so rebases are free.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;sha_at_approval&lt;/code&gt; field is there for audit trail — but the merge gate checks the ledger entry, not the SHA. &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue" rel="noopener noreferrer"&gt;GitHub's native merge queue&lt;/a&gt; handles sequencing but resets GitHub's own review state when branches are rebased. The local ledger sidesteps this entirely. This is the pattern behind &lt;a href="https://htek.dev/articles/cryptographic-approval-gates-ai-agents/" rel="noopener noreferrer"&gt;approval gates that survive code churn&lt;/a&gt;: tie the authorization to the intent, not the implementation.&lt;/p&gt;

&lt;p&gt;The hookflow &lt;code&gt;require-merge-approval.yml&lt;/code&gt; enforces this at dispatch time — every merge-agent invocation must include a &lt;code&gt;PRs: [...]&lt;/code&gt; array, and every entry must exist in the ledger. If it's not in &lt;code&gt;data/merge-queue.json&lt;/code&gt;, the dispatch is blocked before the agent even runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the Deliverable, Not the Code
&lt;/h2&gt;

&lt;p&gt;Non-technical approval of AI-agent PRs is viable only with per-PR preview environments. With Vercel preview deployments attached to every PR and Supabase branch deployments for database-backed features, a non-technical approver reviews a live running version of the change — not a diff. They answer "does this outcome look right?" — a question anyone can answer.&lt;/p&gt;

&lt;p&gt;The reason Sofia can approve 11 PRs in 8 minutes isn't because she's fast at reading code. It's because she's not reading code at all. Every PR gets a &lt;a href="https://vercel.com/docs/deployments/preview-deployments" rel="noopener noreferrer"&gt;Vercel preview deployment&lt;/a&gt; — a full working version of the app with that change applied — linked directly in the Telegram notification.&lt;/p&gt;

&lt;p&gt;For database-backed features, &lt;a href="https://supabase.com/docs/guides/platform/branching" rel="noopener noreferrer"&gt;Supabase branching&lt;/a&gt; gives each PR its own isolated Supabase branch with the migrated schema. Sofia can test the actual feature behavior, not review a migration SQL diff. The question shifts from "does this code look right?" to "does this outcome look right?" That's a question anyone can answer.&lt;/p&gt;

&lt;p&gt;This is a principle I want to be explicit about: &lt;strong&gt;full preview environments per PR are not optional in an agentic workflow&lt;/strong&gt;. They're the mechanism that makes non-technical approval viable. Without them, every PR review is a code review, and you're back to the old throughput ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sofia Multiplier
&lt;/h2&gt;

&lt;p&gt;One non-technical operator. Thirteen PRs merged in a single day, across multiple agent sessions, with reviews — but no diffs read, no GitHub UI opened, no code checked out. If a developer merges 2–3 PRs on a busy day, Sofia's output that day was running at 5-6x that rate — entirely through Telegram taps on Vercel previews.&lt;/p&gt;

&lt;p&gt;The day we hit 13 merged PRs, Sofia ran multiple agent sessions. She approved 11 via &lt;strong&gt;🤖 Agent Merge&lt;/strong&gt;, one via &lt;strong&gt;✅ Merge Now&lt;/strong&gt; (CI was already green), and denied one — a visual regression on mobile she caught from the Vercel preview.&lt;/p&gt;

&lt;p&gt;Her total time on merge decisions: roughly 8 minutes across the full day. Every approval was a Telegram tap on a live preview. No GitHub UI. No diff reading. No checkout-and-run-locally.&lt;/p&gt;

&lt;p&gt;That's the output of a 5-6x multiplier on normal developer throughput — with reviews included — driven by one person who has never written a line of production code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://htek.dev/articles/the-agent-infrastructure-layer-is-here/" rel="noopener noreferrer"&gt;The agent infrastructure layer&lt;/a&gt; isn't about replacing developers. It's about shifting who can participate in the shipping loop. Sofia's multiplier isn't a productivity stat — it's proof that the bottleneck was never the code. It was the merge ceremony around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Your Team Needs First
&lt;/h2&gt;

&lt;p&gt;Before agent-merge can work reliably, your CI/CD stack needs to be at a specific maturity level: CI under 5 minutes, branch protection enforcing status checks, per-PR preview deployments, and agents locked to a deterministic merge tool. Without these, the merge-agent will escalate constantly instead of shipping. The &lt;a href="https://htek.dev/articles/agentic-development-maturity-curve/" rel="noopener noreferrer"&gt;agentic development maturity curve&lt;/a&gt; covers the full picture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI under 5 minutes&lt;/strong&gt; — the merge-agent polls every 30 seconds with a 10-minute timeout. Slow CI means your queue backs up and PRs time out instead of merging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branch protection with required status checks&lt;/strong&gt; — &lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging" rel="noopener noreferrer"&gt;GitHub must enforce CI before any merge&lt;/a&gt; is possible. If your repo allows merging with failing checks, the merge-agent will occasionally ship broken builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-PR preview environments&lt;/strong&gt; — &lt;a href="https://vercel.com/docs/deployments/preview-deployments" rel="noopener noreferrer"&gt;Vercel preview deployments&lt;/a&gt;, Netlify deploy previews, or equivalent. Non-technical approval isn't viable without them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hookflows blocking raw git&lt;/strong&gt; — agents must not be able to call &lt;code&gt;gh pr merge&lt;/code&gt; directly. The hookflow is the enforcement layer, not a convention. Conventions drift; hookflows don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequential merge agent&lt;/strong&gt; — parallel PRs always conflict. The merge-agent processes one at a time, rebase → CI wait → merge. Do not parallelize this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failure mode when you skip these prerequisites is a merge-agent that escalates constantly, which trains your team to ignore it. The system only works if it merges reliably — that reliability comes from the infrastructure layer under it, not from better prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The approval never disappeared. Sofia still approves every PR. What changed is when she approves it — a 3-second Telegram tap on a live running preview — versus a 15-minute code review session she wasn't qualified to do anyway.&lt;/p&gt;

&lt;p&gt;Your AI agents can already write the code. The question is whether your infrastructure can keep pace with them. Build the merge layer first. Then figure out what to automate next.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full implementation — &lt;code&gt;data/merge-queue.json&lt;/code&gt;, the &lt;code&gt;merge_pr&lt;/code&gt; Telegram tool, &lt;code&gt;enforce-merge-pr-tool-only.yml&lt;/code&gt;, &lt;code&gt;require-merge-approval.yml&lt;/code&gt;, and the merge-agent — lives in the rocha-family platform repo (private). The patterns here are the extractable parts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agenticdevelopment</category>
      <category>devops</category>
      <category>automation</category>
      <category>devex</category>
    </item>
    <item>
      <title>The Agent Infrastructure Layer Is Here</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Wed, 17 Jun 2026 17:11:04 +0000</pubDate>
      <link>https://dev.to/htekdev/the-agent-infrastructure-layer-is-here-2lhp</link>
      <guid>https://dev.to/htekdev/the-agent-infrastructure-layer-is-here-2lhp</guid>
      <description>&lt;h2&gt;
  
  
  The Laptop in the Same Room
&lt;/h2&gt;

&lt;p&gt;My laptop is in the same room. It's open. It's charged. I haven't touched it in three hours.&lt;/p&gt;

&lt;p&gt;I'm walking laps around my kitchen with my phone, dispatching agents over Telegram into &lt;a href="https://github.com/github/copilot-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt; sessions. One is reviewing a pull request on the htek.dev Astro 5 site. Two are running content-illustrator jobs against gpt-image-2. Four are on cron schedules I set up weeks ago. Right now, somewhere on my hardware, ~40 &lt;a href="https://htek.dev/articles/agent-mesh-cross-session-communication-copilot-cli/" rel="noopener noreferrer"&gt;domain agents&lt;/a&gt; are alive — most idle, several working, one of them writing this paragraph.&lt;/p&gt;

&lt;p&gt;I am extremely more productive this way. Some of my best work this year happened in the gym, in the car, or pacing the living room while &lt;a href="https://htek.dev/articles/coding-agent-as-life-assistant-nicu/" rel="noopener noreferrer"&gt;Hector Jr.&lt;/a&gt; and the twins napped. The laptop is across the room. I rarely sit at it.&lt;/p&gt;

&lt;p&gt;That's the moment I realized: &lt;strong&gt;the medium of sitting at a keyboard is the bottleneck, not the agent.&lt;/strong&gt; And in Q2 2026, the industry caught up. GitHub's &lt;a href="https://github.blog/changelog/2026-06-02-cloud-and-local-sandboxes-for-github-copilot-now-in-public-preview/" rel="noopener noreferrer"&gt;cloud and local sandboxes for Copilot&lt;/a&gt; entered public preview on June 2, 2026 — running Copilot CLI sessions inside isolated ephemeral Linux environments hosted by GitHub, with &lt;code&gt;copilot --cloud&lt;/code&gt; and &lt;code&gt;/sandbox enable&lt;/code&gt; flipping the runtime layer for everyone. That, plus the broader &lt;a href="https://github.blog/news-insights/product-news/github-copilot-app-the-agent-native-desktop-experience/" rel="noopener noreferrer"&gt;agent-native Copilot desktop direction&lt;/a&gt;, turned persistent agent compute from a personal hack into a product layer. The agent infrastructure layer is here.&lt;/p&gt;

&lt;p&gt;This article is a perspective piece, not a tutorial. It picks up where &lt;a href="https://htek.dev/articles/workflows-are-the-new-commodity/" rel="noopener noreferrer"&gt;my prior essay on workflows as the new asset&lt;/a&gt; left off — and extends that thesis into the persistent-compute era.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Shape of Compute
&lt;/h2&gt;

&lt;p&gt;The new shape of compute is &lt;strong&gt;agents as your interface, not your IDE&lt;/strong&gt;. Persistent compute decouples the agent from the developer's keyboard, so voice and chat replace &lt;code&gt;code .&lt;/code&gt;. The laptop becomes a heavyweight tool reserved for focused work — not the daily driver. For me, it has become a side computer, and the &lt;a href="https://htek.dev/articles/copilot-cli-telegram-bridge-mobile-ai-terminal/" rel="noopener noreferrer"&gt;Telegram bridge into Copilot CLI&lt;/a&gt; is the front door.&lt;/p&gt;

&lt;p&gt;Six months ago I would have called that a hack — "ooh, look, I can SSH from my phone." It isn't. SSH from a phone is unusable for real engineering. What changed is the agent. With a competent agent on the other side of the wire, the phone stops being a remote terminal and becomes a dispatch console.&lt;/p&gt;

&lt;p&gt;Once you taste that, you cannot go back. I have trouble walking &lt;em&gt;toward&lt;/em&gt; my desk now. The keyboard is slower than my voice. The keyboard is slower than my walking. The keyboard pigeonholes you into one task at a time, in one window, in one room.&lt;/p&gt;

&lt;p&gt;A lot of developers won't even feel this yet. They'll insist the laptop is the most productive medium they've ever had — and they'll be right, &lt;em&gt;for the workflow they're running&lt;/em&gt;. The point isn't that laptops are bad. The point is that &lt;strong&gt;agents are so much faster than typing that the medium has to change to keep up with them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl0ju0l3w8g69xatssuka.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl0ju0l3w8g69xatssuka.webp" alt="Architecture flow diagram: developer dispatches over voice or chat into a persistent compute host running sandboxed agents, with a continuous governance loop feeding back into the runtime." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The new shape of agentic development — keyboard input becomes optional, persistent compute hosts long-running agents inside dynamic sandboxes, and a governance loop runs continuously alongside them.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Asset vs the Workflow — the Workflow Is the Winner
&lt;/h2&gt;

&lt;p&gt;The asset versus the workflow — the workflow is the winner. That single sentence is what made persistent compute inevitable. Once the workflow is the asset, the workflow is what scales — and a laptop is the wrong substrate to scale a workflow on. The result is a category shift: compute follows the asset, and the asset moved up the stack.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://htek.dev/articles/workflows-are-the-new-commodity/" rel="noopener noreferrer"&gt;prior essay&lt;/a&gt; I argued that code stopped being the asset around the time agents got good enough to write it. The asset moved up the stack: into &lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt;, agent design, and the harness around the model. That essay was about &lt;em&gt;what&lt;/em&gt; won. This one is about &lt;em&gt;what wins demand&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Workflows demand somewhere persistent to live. A laptop can host one or two agents comfortably. Run ten and the OS starts swapping. Run forty and the laptop becomes a single point of failure for your entire process — close the lid and your work stops, sleep mode kills sessions, a Windows Update reboot wipes a day of running cron jobs. I've watched it happen.&lt;/p&gt;

&lt;p&gt;Persistent agent compute solves the structural problem the workflow created. It is the natural next step: if the workflow is the asset, the workflow needs infrastructure. Not "more RAM on my MacBook" infrastructure — &lt;em&gt;real&lt;/em&gt; infrastructure, with uptime, isolation, and compute that doesn't care whether your laptop is plugged in.&lt;/p&gt;

&lt;p&gt;That's why the persistent agent compute category shipped real product in Q2 2026 instead of staying hypothetical. GitHub put cloud sandboxes into public preview. Microsoft published guidance on &lt;a href="https://techcommunity.microsoft.com/blog/appsonazureblog/part-2-build-long-running-ai-agents-on-azure-app-service-with-microsoft-agent-fr/4465825" rel="noopener noreferrer"&gt;building long-running AI agents on Azure App Service with the Microsoft Agent Framework&lt;/a&gt;. The Microsoft and GitHub ecosystem hit the same wall at roughly the same time — laptops can't host real workflows — and reached for the same answer: &lt;strong&gt;move the agent off the developer's machine.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust Is the Gate
&lt;/h2&gt;

&lt;p&gt;The only thing standing between us and fully autonomous agents is trust, and sandboxing is the trust unlock. Long-running agents accumulate blast radius the longer they run, so isolation has to live at the runtime layer — not in policy documents. GitHub's cloud sandbox model and OpenShell-class policy runtimes are both expressions of this same principle: lock the runtime, not the developer.&lt;/p&gt;

&lt;p&gt;Persistent compute amplifies the problem. An agent that runs for five minutes can only break so much; an agent that runs for five days can break a lot.&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://github.com/github/copilot-cli" rel="noopener noreferrer"&gt;contributed to GitHub Copilot CLI&lt;/a&gt; specifically to integrate with &lt;a href="https://htek.dev/articles/nvidia-openshell-sandbox-ai-agents/" rel="noopener noreferrer"&gt;NVIDIA OpenShell&lt;/a&gt; because I genuinely believe sandboxing is the last layer we need to ship fully autonomous agents into production. Everything else — &lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;hookflows&lt;/a&gt;, &lt;a href="https://htek.dev/articles/stop-building-fat-copilot-extensions/" rel="noopener noreferrer"&gt;governed extensions&lt;/a&gt;, &lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;memory&lt;/a&gt; — is necessary. Sandboxing is sufficient.&lt;/p&gt;

&lt;p&gt;That's why I got excited when GitHub started rolling out &lt;a href="https://docs.github.com/copilot/concepts/about-cloud-and-local-sandboxes" rel="noopener noreferrer"&gt;cloud and local sandboxes for Copilot&lt;/a&gt;. They are doing the right thing: &lt;strong&gt;redesigning the Copilot compute layer to align to the agent era, starting with sandboxing.&lt;/strong&gt; That is exactly the right place to put it. My whole platform philosophy is &lt;em&gt;let the right thing to do be the easy thing to do&lt;/em&gt; — and with sandboxing built into the compute layer, the right thing to do is the &lt;strong&gt;only&lt;/strong&gt; thing you can do. There is no "skip the sandbox" path. That is a structural win for the ecosystem.&lt;/p&gt;

&lt;p&gt;I'll be honest: I'm not running a sandbox today. I'm running a strict governance process — hookflows that block raw &lt;code&gt;git&lt;/code&gt;, &lt;a href="https://htek.dev/articles/stop-building-fat-copilot-extensions/" rel="noopener noreferrer"&gt;governed extensions&lt;/a&gt; instead of fat tools, and continuous review of agent behavior. It works. It also has potential holes. &lt;strong&gt;Everyone should be doing sandboxing in some way or another.&lt;/strong&gt; Sandboxing is the layer that closes the cracks your governance leaves open — the strongest assurance available that nothing slips through. It is the last layer of protection, &lt;em&gt;not&lt;/em&gt; the only one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhcgzdxe8d0m6senq857z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhcgzdxe8d0m6senq857z.webp" alt="Concentric trust-layering diagram with the agent at the core, surrounded by an inner harness ring, a dynamic sandbox ring, and an outer continuous-governance ring." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Defense in depth for long-running agents — the harness governs the agent's tools, the sandbox isolates the runtime, and a continuous governance loop closes the cracks the static layers leave behind.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Still Missing: Absurd Granularity
&lt;/h2&gt;

&lt;p&gt;What's still missing across the category is dynamic granularity. A static sandbox — defined once, locked down, reviewed quarterly — becomes another IT layer of governance, and agents route around IT layers the same way developers always have. The fix is per-binary, hot-reloadable policy that the agent itself can extend at run time, gated by deterministic and non-deterministic review.&lt;/p&gt;

&lt;p&gt;Agents move faster than quarterly review cycles. The sandbox has to move with them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://htek.dev/articles/nvidia-openshell-sandbox-ai-agents/" rel="noopener noreferrer"&gt;NVIDIA OpenShell&lt;/a&gt; is the benchmark here. OpenShell exposes per-binary network policies in a hot-reloadable policy file. That means an agent can declare &lt;em&gt;this binary&lt;/em&gt; needs to talk to &lt;em&gt;this endpoint&lt;/em&gt;, and the policy adjusts in real time, gated by deterministic and non-deterministic review. That is the level of malleability the rest of the category has to match.&lt;/p&gt;

&lt;p&gt;I'm hoping the Copilot sandbox surface evolves in this direction — that it lets the agent declare what individual endpoints it needs, with absurd granularity, and the policy engine adapts. Early signals from the &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/copilot/concepts/agents/cloud-agent/about-cloud-agent" rel="noopener noreferrer"&gt;Copilot cloud agent docs&lt;/a&gt; suggest GitHub is thinking about this seriously. If sandboxes stay rigid, they become the next bureaucratic layer everyone routes around. If they go dynamic, they become the foundation for fully autonomous agents.&lt;/p&gt;

&lt;p&gt;This is a &lt;em&gt;constructive&lt;/em&gt; push, not a critique. GitHub is in the best position in the industry to ship dynamic sandboxing because the Copilot CLI already understands the agent's intent at the tool-call level. The pieces are there. The question is whether the policy layer evolves fast enough to keep pace with the agents running inside it.&lt;/p&gt;

&lt;p&gt;My &lt;a href="https://htek.dev/articles/stop-building-fat-copilot-extensions/" rel="noopener noreferrer"&gt;Copilot CLI extensions&lt;/a&gt; approach is a version of dynamic granularity — but at the tool-governance layer, not the runtime layer. It is the second line of defense. Sandboxing is the first. Both should exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance Has to Move at Agent Speed
&lt;/h2&gt;

&lt;p&gt;Persistent agents change the governance calculus completely. Continuous governance is non-negotiable, because long-running agents accumulate risk continuously, and the governance loop itself has to be a workflow — not a policy document. &lt;strong&gt;It is not about what governance you have enabled. It is about what workflows are enabling governance.&lt;/strong&gt; The same DevOps discipline DORA has measured for a decade — small batches, automated checks, fast feedback — applies, but at agent speed.&lt;/p&gt;

&lt;p&gt;A static "we have a security review" model breaks the moment an agent runs for 72 hours and touches twelve repos. The governance loop has to be &lt;em&gt;another agent&lt;/em&gt;, finding holes, patching them, tightening permissions, then doing it again the next hour. This is the harness primitive set I rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;Hookflows&lt;/a&gt;&lt;/strong&gt; — block dangerous tool calls (raw &lt;code&gt;git&lt;/code&gt;, raw &lt;code&gt;curl&lt;/code&gt;, raw &lt;code&gt;Invoke-WebRequest&lt;/code&gt;) and replace them with governed equivalents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic tools&lt;/strong&gt; — &lt;a href="https://htek.dev/articles/stop-building-fat-copilot-extensions/" rel="noopener noreferrer"&gt;governed extensions&lt;/a&gt; that own a domain instead of letting agents use raw shell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://htek.dev/articles/agent-mesh-cross-session-communication-copilot-cli/" rel="noopener noreferrer"&gt;Domain agents&lt;/a&gt;&lt;/strong&gt; — bounded responsibility, structured handoffs, no monolith god-prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;Memory management&lt;/a&gt;&lt;/strong&gt; — durable context that survives session compaction and feeds the next agent run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous governance agents&lt;/strong&gt; — meta-agents that audit the rest of the system, find new holes, and propose patches as PRs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the part most teams skip. The governance strategy itself must be a workflow, not a document. The &lt;a href="https://cloud.google.com/blog/products/devops-sre/announcing-the-2024-dora-report" rel="noopener noreferrer"&gt;2024 Accelerate State of DevOps Report&lt;/a&gt; is direct about this: high performers win on disciplined process, not raw speed — and that pattern carries into agentic development. Persistent compute makes it practical: a &lt;code&gt;governance-auditor&lt;/code&gt; agent can run on a cron, every hour, forever, with no laptop required. Two months ago I would have said that's overkill. Today it is the only model that keeps up.&lt;/p&gt;

&lt;p&gt;If you want a deeper walkthrough of the harness primitives behind this, the &lt;a href="https://htek.dev/articles/agent-harnesses-controlling-ai-agents-2026/" rel="noopener noreferrer"&gt;agent harness post&lt;/a&gt; and &lt;a href="https://htek.dev/articles/7-layer-ai-governance-stack/" rel="noopener noreferrer"&gt;governance stack&lt;/a&gt; cover the architecture in detail. Persistent compute does not change &lt;em&gt;what&lt;/em&gt; those primitives are — it changes &lt;em&gt;how often&lt;/em&gt; they have to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leave the Keyboard
&lt;/h2&gt;

&lt;p&gt;The agent infrastructure layer is here, and the shape is finally clear. Compute is moving off the laptop into &lt;a href="https://docs.github.com/copilot/concepts/about-cloud-and-local-sandboxes" rel="noopener noreferrer"&gt;Copilot's cloud sandboxes&lt;/a&gt; and Azure-hosted long-running agents. Sandboxing is moving into the compute layer by default. Governance is moving from a document to a workflow that runs at agent speed. The category is real, the tools are &lt;a href="https://github.com/github/copilot-cli/releases" rel="noopener noreferrer"&gt;shipping in Copilot CLI releases&lt;/a&gt;, and the laptop is no longer where development happens.&lt;/p&gt;

&lt;p&gt;The change you can make this week is smaller and more personal: &lt;strong&gt;try a different medium for one day.&lt;/strong&gt; Set up a &lt;a href="https://htek.dev/articles/copilot-cli-telegram-bridge-mobile-ai-terminal/" rel="noopener noreferrer"&gt;Telegram bridge&lt;/a&gt; to your CLI. Dispatch one agent over voice while you walk the dog. Notice how much the keyboard was constraining you. The first day feels weird. The second day, the laptop starts to feel like a desktop tower from 2008 — useful for the heavy stuff, irrelevant for everything else.&lt;/p&gt;

&lt;p&gt;Once you have done that, the rest of the stack follows naturally. You'll want persistent compute, because closing your laptop will start to feel like a limit. You'll want sandboxing, because long-running agents will start touching things you care about. You'll want continuous governance, because static rules will start to feel slow. The infrastructure layer is here precisely because thousands of developers are about to want all three.&lt;/p&gt;

&lt;p&gt;The right thing to do is becoming the easy thing to do. The only thing left is to stop staring at the keyboard.&lt;/p&gt;

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

&lt;p&gt;External sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.blog/changelog/2026-06-02-cloud-and-local-sandboxes-for-github-copilot-now-in-public-preview/" rel="noopener noreferrer"&gt;Cloud and local sandboxes for GitHub Copilot now in public preview — GitHub Changelog (June 2, 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/copilot/concepts/about-cloud-and-local-sandboxes" rel="noopener noreferrer"&gt;About cloud and local sandboxes for GitHub Copilot — GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/news-insights/product-news/github-copilot-app-the-agent-native-desktop-experience/" rel="noopener noreferrer"&gt;GitHub Copilot app: the agent-native desktop experience — GitHub Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/enterprise-cloud@latest/copilot/concepts/agents/cloud-agent/about-cloud-agent" rel="noopener noreferrer"&gt;About the Copilot cloud agent — GitHub Enterprise Cloud Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/github/copilot-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/github/copilot-cli/releases" rel="noopener noreferrer"&gt;GitHub Copilot CLI releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcommunity.microsoft.com/blog/appsonazureblog/part-2-build-long-running-ai-agents-on-azure-app-service-with-microsoft-agent-fr/4465825" rel="noopener noreferrer"&gt;Build Long-Running AI Agents on Azure App Service with Microsoft Agent Framework — Microsoft Tech Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/products/devops-sre/announcing-the-2024-dora-report" rel="noopener noreferrer"&gt;Announcing the 2024 DORA Report — Google Cloud Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/changelog/2026-02-25-github-copilot-cli-is-now-generally-available/" rel="noopener noreferrer"&gt;GitHub Copilot CLI is now generally available — GitHub Changelog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prior articles in this thread:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conceptual prequel — &lt;a href="https://htek.dev/articles/workflows-are-the-new-commodity/" rel="noopener noreferrer"&gt;Code Is No Longer the Asset. Workflows Are the New Commodity.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/nvidia-openshell-sandbox-ai-agents/" rel="noopener noreferrer"&gt;NVIDIA OpenShell — The Sandbox Your AI Agents Should Be Running In&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/copilot-cli-native-sandboxing-agent-security/" rel="noopener noreferrer"&gt;Copilot CLI Native Sandboxing — What This Means for Agent Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/stop-building-fat-copilot-extensions/" rel="noopener noreferrer"&gt;Stop Building Fat Copilot Extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;Hookflows: Governed Git for AI Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/agent-mesh-cross-session-communication-copilot-cli/" rel="noopener noreferrer"&gt;Agent Mesh: Cross-Session Communication for Copilot CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/copilot-cli-telegram-bridge-mobile-ai-terminal/" rel="noopener noreferrer"&gt;Your AI Terminal, In Your Pocket: Copilot CLI to Telegram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/agent-harnesses-controlling-ai-agents-2026/" rel="noopener noreferrer"&gt;Agent Harnesses — Controlling AI Agents in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/7-layer-ai-governance-stack/" rel="noopener noreferrer"&gt;The 7-Layer AI Governance Stack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agenticdevelopment</category>
      <category>github</category>
      <category>platformengineering</category>
      <category>security</category>
    </item>
    <item>
      <title>AI Harness v0.6.0 — Harness as Code Gets Its Reference Implementation</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 16 Jun 2026 22:45:21 +0000</pubDate>
      <link>https://dev.to/htekdev/ai-harness-v060-harness-as-code-gets-its-reference-implementation-1khj</link>
      <guid>https://dev.to/htekdev/ai-harness-v060-harness-as-code-gets-its-reference-implementation-1khj</guid>
      <description>&lt;p&gt;Most AI harnesses start as a prompt and a wrapper. They get to v1.0 by&lt;br&gt;
accumulating branches in the wrapper. &lt;strong&gt;AI Harness took the opposite path:&lt;/strong&gt;&lt;br&gt;
codify governance as typed artifacts, then make the wrapper as small as&lt;br&gt;
possible.&lt;/p&gt;

&lt;p&gt;v0.6.0 is the first release where that bet looks proven.&lt;/p&gt;

&lt;p&gt;If you've been following the &lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;Harness as Code thesis&lt;/a&gt;,&lt;br&gt;
this is the release where the runtime catches up to the philosophy.&lt;/p&gt;
&lt;h2&gt;
  
  
  What v0.6.0 actually changes
&lt;/h2&gt;

&lt;p&gt;Four things matter in this release. Everything else is supporting work.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Typed artifact bundles are real
&lt;/h3&gt;

&lt;p&gt;Shape A bundles — &lt;code&gt;.harness/{plugins,builtins,overrides}/*.md&lt;/code&gt; — are now&lt;br&gt;
first-class. The bundle loader (PR #123) closed the gap where the artifact&lt;br&gt;
registry already understood &lt;code&gt;harness_artifact/v1alpha1&lt;/code&gt; declarations but&lt;br&gt;
&lt;code&gt;serve&lt;/code&gt; and &lt;code&gt;validate&lt;/code&gt; quietly ignored them.&lt;/p&gt;

&lt;p&gt;One file = one capability bundle. Tools, hooks, and prompts that belong to&lt;br&gt;
the same governance unit live in the same artifact.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The agent loop is hardened
&lt;/h3&gt;

&lt;p&gt;A strict &lt;code&gt;finish_reason&lt;/code&gt; guard now sits at the top of the loop (PRs #121, #123):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;finish_reason&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;end_turn&lt;/code&gt;, &lt;code&gt;""&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Fall through to a final answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;length&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retriable error — context truncated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;content_filter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hard error — no silent recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anything else, no tool calls&lt;/td&gt;
&lt;td&gt;Retriable error — no silent stop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No more "agent quietly stopped on turn 14 and we don't know why."&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Reference docs are complete
&lt;/h3&gt;

&lt;p&gt;Every public surface has an exhaustive reference page now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;harness.md&lt;/code&gt; frontmatter — every field, every default, every &lt;code&gt;validate()&lt;/code&gt; check&lt;/li&gt;
&lt;li&gt;Tool artifact schema — file shape, parameters, Starlark dialect, &lt;code&gt;async&lt;/code&gt; reserved&lt;/li&gt;
&lt;li&gt;Hook artifact schema — full event catalog, payload shapes, decision contract, &lt;code&gt;when:&lt;/code&gt; semantics&lt;/li&gt;
&lt;li&gt;Starlark built-ins — every builtin from &lt;code&gt;scripting.Engine.makeBuiltins&lt;/code&gt;, per-module&lt;/li&gt;
&lt;li&gt;CLI — every subcommand, flag, env var, exit code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No more "read the source." The docs are now the contract.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. The live bot is governed
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://t.me/htekdevaiharness" rel="noopener noreferrer"&gt;&lt;code&gt;@htekdevaiharness&lt;/code&gt;&lt;/a&gt; on Telegram runs the&lt;br&gt;
same Shape A bundles you'd ship to your own team:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;harness validate &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="go"&gt;21 tools registered (across harness.md + 2 plugin bundles)
5 hooks registered
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That count comes from a &lt;code&gt;notes-bundle&lt;/code&gt; (note save/list + audit hook) and a&lt;br&gt;
&lt;code&gt;safety-bundle&lt;/code&gt; (command guard + output redactor + status tool) both loaded&lt;br&gt;
as typed artifacts. The same loader. The same precedence rules. The same&lt;br&gt;
docs you'd read.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why typed artifact bundles matter
&lt;/h2&gt;

&lt;p&gt;This is the conceptual centerpiece, and it's where AI Harness takes the&lt;br&gt;
strongest position against everything else in the category.&lt;/p&gt;

&lt;p&gt;Most "extension" systems give you one file per capability and pretend that's&lt;br&gt;
the answer. The reality: a real capability is rarely &lt;em&gt;one&lt;/em&gt; tool. It's a tool&lt;br&gt;
plus a hook plus a guard plus a default prompt fragment. Splitting those&lt;br&gt;
across four files breaks composability — you can no longer move "the safety&lt;br&gt;
capability" between repos as one diff.&lt;/p&gt;

&lt;p&gt;Shape A bundles fix that. Each &lt;code&gt;.md&lt;/code&gt; file declares a single &lt;strong&gt;capability bundle&lt;/strong&gt;:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;harness_artifact/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plugin&lt;/span&gt;            &lt;span class="c1"&gt;# plugin | builtin | override&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;safety-bundle&lt;/span&gt;
&lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Safety bundle&lt;/span&gt;

&lt;span class="s"&gt;Tools, hooks, and prompts that govern destructive operations.&lt;/span&gt;

&lt;span class="c1"&gt;## Tool: command_guard&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;## Hook: tool.pre / output_redactor&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Composition is &lt;strong&gt;deterministic&lt;/strong&gt;. Precedence is declared at the kind level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override &amp;gt; harness &amp;gt; builtin &amp;gt; plugin &amp;gt; model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per-turn evaluation re-checks each artifact's &lt;code&gt;when:&lt;/code&gt; predicate every turn,&lt;br&gt;
not just at startup. An artifact that's inactive on turn 3 can light up on&lt;br&gt;
turn 4 without restarting the agent.&lt;/p&gt;

&lt;p&gt;This is the line that separates &lt;strong&gt;"extensions"&lt;/strong&gt; from &lt;strong&gt;Harness as Code&lt;/strong&gt;:&lt;br&gt;
the unit of governance is the &lt;em&gt;bundle&lt;/em&gt;, not the individual file. You can&lt;br&gt;
review one diff. You can move one folder. You can audit one artifact. The&lt;br&gt;
runtime composes them deterministically.&lt;/p&gt;
&lt;h2&gt;
  
  
  Things you can actually inspect now
&lt;/h2&gt;

&lt;p&gt;Three commands that didn't quite work two releases ago and now are the&lt;br&gt;
daily-driver:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;harness validate -v&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Registers every artifact, runs every parser, prints a per-bundle tool/hook&lt;br&gt;
count. On the live bot today: &lt;strong&gt;21 tools / 5 hooks&lt;/strong&gt; across &lt;code&gt;harness.md&lt;/code&gt; +&lt;br&gt;
two plugin bundles. If the number doesn't match what you expect, your&lt;br&gt;
bundle isn't loading. That's the loop.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;harness context --verbose&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Shows what the agent saw on a given turn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which chunks were assembled into the system prompt&lt;/li&gt;
&lt;li&gt;where each chunk came from (which artifact, which file)&lt;/li&gt;
&lt;li&gt;which artifacts were active vs inactive&lt;/li&gt;
&lt;li&gt;which &lt;code&gt;when:&lt;/code&gt; predicates passed&lt;/li&gt;
&lt;li&gt;total token spend, broken down by source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Context observability is not an afterthought. It is shipped.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;harness artifacts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Flat list of every loaded artifact with its priority, kind, source file,&lt;br&gt;
and active/inactive state. Useful when you need to answer "is this hook&lt;br&gt;
actually firing?" without grepping through bundles.&lt;/p&gt;
&lt;h2&gt;
  
  
  What's still off the menu
&lt;/h2&gt;

&lt;p&gt;Honesty matters. v0.6.0 is &lt;strong&gt;not&lt;/strong&gt; a "we figured it all out" release.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compaction engine vs hooks&lt;/strong&gt; — open question (#69 / roadmap). The leading
candidate is hooks-driven compaction in v0.7.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory persistence&lt;/strong&gt; — flat-files today; SQLite is on the table for v0.7.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-agent supervision&lt;/strong&gt; — primitive level, not orchestration level. Phase
7 territory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async tool calls&lt;/strong&gt; — &lt;code&gt;async:&lt;/code&gt; is &lt;strong&gt;reserved&lt;/strong&gt; in the tool schema (parsed
but not propagated through &lt;code&gt;ToolConfig&lt;/code&gt;). Wired in Phase 3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;agent.stop&lt;/code&gt; hook event&lt;/strong&gt; — the strict &lt;code&gt;finish_reason&lt;/code&gt; guard ships in
v0.6.0, but the proper hook primitive (issue #104) is held for v0.7.0 so
it can get its own design pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need any of those today, you're early. That's fine. The core's&lt;br&gt;
&lt;em&gt;shape&lt;/em&gt; is what we're committing to in v0.6.0; the edges are still moving.&lt;/p&gt;

&lt;p&gt;The pre-1.0 schema-evolution clause stays in effect: artifact frontmatter&lt;br&gt;
fields can still change between minor releases. The CHANGELOG calls every&lt;br&gt;
break out explicitly.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to try it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/htekdev/ai-harness/cmd/harness@latest
harness init my-agent
&lt;span class="nb"&gt;cd &lt;/span&gt;my-agent
harness validate &lt;span class="nt"&gt;-v&lt;/span&gt;
harness serve &lt;span class="nt"&gt;--source&lt;/span&gt; stdin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then drop a Shape A bundle into &lt;code&gt;.harness/plugins/&lt;/code&gt;:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;harness_artifact/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plugin&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;my-first-bundle&lt;/span&gt;
&lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;## Tool: hello&lt;/span&gt;
&lt;span class="s"&gt;Say hello and exit.&lt;/span&gt;

&lt;span class="c1"&gt;## Hook: tool.post / log-everything&lt;/span&gt;
&lt;span class="s"&gt;Print every tool call to stderr.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run &lt;code&gt;harness validate -v&lt;/code&gt;. The tool/hook count should go up. That's the&lt;br&gt;
loop. That's the whole product surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger arc
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;v0.4.0&lt;/strong&gt; was the first usable harness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v0.5.0&lt;/strong&gt; was the first one with proper claims verification (Ralph loop
at the delegation boundary).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v0.6.0 is the first one where the artifact model, the loop, and the
docs all line up with the Harness-as-Code thesis.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the milestone worth marking. v0.7 is async, memory persistence, and&lt;br&gt;
the compaction engine. After that, v1.0 is a positioning question, not an&lt;br&gt;
engineering one.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/htekdev/ai-harness" rel="noopener noreferrer"&gt;github.com/htekdev/ai-harness&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://htekdev.github.io/ai-harness/" rel="noopener noreferrer"&gt;htekdev.github.io/ai-harness&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live bot:&lt;/strong&gt; &lt;a href="https://t.me/htekdevaiharness" rel="noopener noreferrer"&gt;@htekdevaiharness&lt;/a&gt; on Telegram&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Companion piece:&lt;/strong&gt; &lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;What Is Harness as Code?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category survey:&lt;/strong&gt; &lt;a href="https://htek.dev/articles/all-agent-harnesses-live-comparison/" rel="noopener noreferrer"&gt;Live comparison of agent harnesses&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've been waiting for "the small one with real governance," this is it.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>agenticdevelopment</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>Agents Are Your New Customer</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 16 Jun 2026 20:58:38 +0000</pubDate>
      <link>https://dev.to/htekdev/agents-are-your-new-customer-367h</link>
      <guid>https://dev.to/htekdev/agents-are-your-new-customer-367h</guid>
      <description>&lt;p&gt;I watched the &lt;a href="https://www.youtube.com/watch?v=icUycAMC1h0" rel="noopener noreferrer"&gt;WorkOS Auth.md video&lt;/a&gt; this morning and felt two things simultaneously.&lt;/p&gt;

&lt;p&gt;Amazed. And annoyed.&lt;/p&gt;

&lt;p&gt;Amazed because this is real. Annoyed because I want to be further in the frontier — and watching someone else ship the vocabulary for the category I've been working in quietly for months has that effect on you.&lt;/p&gt;

&lt;p&gt;Auth.md is a new open protocol from WorkOS that lets AI agents authenticate to a service on behalf of a user — without a browser, without a sign-up form, without a human in the loop. It's a Markdown file you publish at your domain. Agents read it, parse the flows, pick a registration path, and either present a verified identity assertion from a trusted provider or walk the user through a six-digit OTP claim. Standard OAuth credentials come out the other end.&lt;/p&gt;

&lt;p&gt;That's the mechanism. But the line that broke my brain wasn't technical.&lt;/p&gt;

&lt;p&gt;It was this: &lt;strong&gt;"Agents are your new customer."&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Line That Inverted Everything
&lt;/h2&gt;

&lt;p&gt;"Agents are your new customer. Build things that agents need, build things that agents want."&lt;/p&gt;

&lt;p&gt;That's the startup mantra — "know your customer" — flipped on its head. For two decades, "customer" meant a human. A person with a browser. A person who fills in a form, clicks a button, reads an onboarding email. That's who infrastructure was built for.&lt;/p&gt;

&lt;p&gt;Auth.md argues that the entity hitting your API might not be that person anymore. It might be an autonomous process running on their behalf. And if you haven't designed your API to receive that process, it will bounce off you. It will return a 401. It will give up, or it will ask the human to stop what they're doing and manually paste in an API key like it's 2014.&lt;/p&gt;

&lt;p&gt;The scope of that shift is TCP/IP-scale. When the internet came out, we defined the primitives that let machines talk to each other — TCP, IP, HTTP. Auth.md is proposing a primitive for the agent era: a standard way for an autonomous actor to present itself to a service that doesn't know it. No proprietary handshakes. No per-vendor integrations. Just: here's who I am, here's who I'm acting for, here's the credential.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://workos.com/auth-md" rel="noopener noreferrer"&gt;WorkOS Auth.md&lt;/a&gt; is open. Not WorkOS-only. Any app can publish an &lt;code&gt;auth.md&lt;/code&gt; file. Any agent can read one. The protocol is built on standards you already know — OAuth 2.0, OIDC, Protected Resource Metadata (RFC 9728), JWT-based identity assertions. No new crypto. No new key distribution. The clever part is the discovery layer and the two flows that handle the cases existing OAuth couldn't — because existing OAuth always assumed a human in the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Was Already Here
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly42iup64snfwe0zascj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly42iup64snfwe0zascj.webp" alt="Three pillars of agent-first infrastructure: MeshWire, Hookflows, and Content Encryption" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Three pieces of agent-first infrastructure I'd been building before Auth.md gave me the vocabulary: MeshWire, Hookflows, and content encryption.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the part that hit different.&lt;/p&gt;

&lt;p&gt;I've been building agent-first infrastructure for months. Not because I had a thesis about it. Because that's just what the problems demanded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://meshwire.io" rel="noopener noreferrer"&gt;&lt;strong&gt;Meshwire&lt;/strong&gt;&lt;/a&gt; — I built it so agents can discover and connect to each other across sessions. Not so engineers can wire up a graph in a config file. For agents. So an agent running in one Copilot CLI session can find and talk to another agent running in a different session, across a network, without either of them knowing ahead of time that the other exists. My &lt;a href="https://htek.dev/articles/stop-connecting-your-agents-one-by-one/" rel="noopener noreferrer"&gt;MeshWire launch post&lt;/a&gt; is literally titled "Stop Connecting Your Agents One by One" — a complaint about the human-centric wiring model being applied to agent infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://htek.dev/articles/aspect-oriented-programming-ai-agents-hookflows/" rel="noopener noreferrer"&gt;&lt;strong&gt;Hookflows&lt;/strong&gt;&lt;/a&gt; — governance for agents, not humans. The whole point of a hookflow is that an agent triggers a hook at a defined checkpoint, and the platform can observe, validate, or block what happens next — without a human watching the terminal. I built those because I needed a way to enforce behavioral contracts across a fleet of agents that were operating autonomously. No human approval gates. Agent-to-agent accountability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content encryption&lt;/strong&gt; — I built this so content could be consumed by agents with verified access, not just by humans with passwords. The whole model is: agent presents credentials, gets decrypted content, acts on it. There's no human login flow because I never intended one.&lt;/p&gt;

&lt;p&gt;Auth.md is the vocabulary I didn't have for what I was doing.&lt;/p&gt;

&lt;p&gt;I wasn't building "AI tools with some backend services attached." I was building services whose primary consumer was going to be an agent. I just didn't have a word for that orientation. I couldn't point at the Auth.md spec and say: "This is the problem space I'm living in." Now I can.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Auth.md Actually Solves
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2rl4cc9lhbeo1gdijfxo.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2rl4cc9lhbeo1gdijfxo.webp" alt="Auth.md two flows: Agent Verified and User Claimed" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Auth.md's two registration flows: Agent Verified (trusted provider attestation) and User Claimed (OTP binding). Both terminate in scoped, revocable OAuth credentials.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The problem Auth.md addresses is deceptively simple: &lt;em&gt;an agent hits your API, gets a 401. What does it do next?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before Auth.md, the options were bad. The agent gives up. The agent interrupts the user to go create an account manually. The app ships a bespoke agent-registration endpoint that no other agent is trained to understand. None of these scale.&lt;/p&gt;

&lt;p&gt;Auth.md solves this with two flows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Verified&lt;/strong&gt; — A trusted agent provider (think: any platform whose agents act on behalf of authenticated users) signs an identity assertion — an &lt;a href="https://github.com/workos/auth.md" rel="noopener noreferrer"&gt;ID-JAG&lt;/a&gt; — vouching for the user. The agent presents it to your &lt;code&gt;/agent-auth&lt;/code&gt; endpoint. You verify the JWT against the provider's JWKS. You JIT-provision a user record. You issue a scoped credential. No OTP. No human in the loop. If you've ever JIT-provisioned users from Google Sign-In, this is structurally the same — just with an agent as the trust intermediary instead of a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Claimed&lt;/strong&gt; — For when there's no trusted provider attestation. Your app issues a claim token, sends the user a one-time link, renders a six-digit OTP, and the agent completes the binding on the user's behalf. Slightly more state to manage, but it gets to the same place: a scoped, revocable credential tied to a real user that an agent can present on every subsequent request.&lt;/p&gt;

&lt;p&gt;What makes this elegant is what it doesn't do. No new crypto. No new user model. The auth.md file itself is plain Markdown — &lt;a href="https://github.com/workos/auth.md" rel="noopener noreferrer"&gt;discoverable at &lt;code&gt;https://yourapp.com/auth.md&lt;/code&gt;&lt;/a&gt;, parseable by any agent that can read documentation. The protocol layers on top of standards you already have. The lift to integrate is five steps and a few dozen lines.&lt;/p&gt;

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

&lt;p&gt;Authentication is actually the entry point to a bigger problem: &lt;em&gt;how does an agent-accessible service establish trust, issue credentials, and eventually handle payment?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Right now, most SaaS assumes a person is clicking "Subscribe" somewhere. An agent can't do that. It can't fill in a credit card form. It can't reason through a pricing page and select a plan. The "customer journey" is entirely human-shaped, which means the moment your customer is an agent, the entire onboarding funnel is broken.&lt;/p&gt;

&lt;p&gt;Auth.md solves the authentication layer. Payment and billing for agents is a harder problem still unsolved — but it's the natural next step once authentication is a primitive. Who pays for the agent's API calls? Is it the user on whose behalf the agent acts? Is it the agent platform? How does a service know which organizational billing context to attach to a credential issued to an autonomous process?&lt;/p&gt;

&lt;p&gt;These are the questions that emerge once you accept the premise: &lt;strong&gt;agents are your new customer&lt;/strong&gt;. Not a stretch goal. Not a 2030 problem. Now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rethink Your Customer
&lt;/h2&gt;

&lt;p&gt;I'm not alone in building things agents need before having the vocabulary for it. I hear versions of this story regularly from engineers who've been quietly adding agent-friendly endpoints, building fleet-management tooling, or designing consent models for &lt;a href="https://htek.dev/articles/how-to-build-governed-ai-agent-systems/" rel="noopener noreferrer"&gt;governed agent systems&lt;/a&gt; that have no analog in the human-centric playbook.&lt;/p&gt;

&lt;p&gt;The vocabulary matters. Not because you couldn't build without it — clearly you could — but because having it changes what you reach for. It changes how you pitch. It changes who you're building with. "This is agent-first infrastructure" is a different conversation than "this is a developer tool with an API." It signals a different design orientation, a different set of tradeoffs, a different kind of early adopter.&lt;/p&gt;

&lt;p&gt;Auth.md gives us a shared language. A spec that says: yes, this is a real problem, here are the flows, here's the primitive, build on it.&lt;/p&gt;

&lt;p&gt;If you're reading this and something in you is going &lt;em&gt;yes, that's the thing I've been feeling&lt;/em&gt; — you're not alone. The shift to agents-as-customer is already happening. You might have been building for it without naming it. That's fine. You're not behind. You're in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;WorkOS's &lt;a href="https://workos.com/blog/agent-registration-with-auth-md" rel="noopener noreferrer"&gt;Auth.md&lt;/a&gt; is one of the most important developer primitives to ship in the agent era. Not because it's complex — it's deliberately simple. Because it's the first standardized answer to "how does an agent authenticate to a service it's never seen?" on top of standards we already trust.&lt;/p&gt;

&lt;p&gt;More importantly, it named a category.&lt;/p&gt;

&lt;p&gt;"Agents are your new customer" isn't hype. It's a reframe of something that's already true for a growing number of services. Auth.md is the infrastructure that makes that sentence actionable — something you can implement today, not something you wait for a platform to do for you.&lt;/p&gt;

&lt;p&gt;I'm building in this space. If you're building in this space, or starting to feel the pull toward it, the vocabulary is here. Start from the &lt;a href="https://github.com/workos/auth.md" rel="noopener noreferrer"&gt;Auth.md spec on GitHub&lt;/a&gt;. Publish your &lt;code&gt;auth.md&lt;/code&gt;. See how your service looks through the eyes of an agent trying to get in the door.&lt;/p&gt;

&lt;p&gt;You might be closer to agent-first than you think.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Relevant context from my own work: &lt;a href="https://htek.dev/articles/stop-connecting-your-agents-one-by-one/" rel="noopener noreferrer"&gt;Stop Connecting Your Agents One by One&lt;/a&gt; — MeshWire's origin story. &lt;a href="https://htek.dev/articles/hookflows-governed-git-for-ai-agents/" rel="noopener noreferrer"&gt;Hookflows: Governed Git for AI Agents&lt;/a&gt; — building governance for agents, not humans. &lt;a href="https://htek.dev/articles/three-layers-your-ai-agent-is-missing/" rel="noopener noreferrer"&gt;Three Layers Your AI Agent Is Missing&lt;/a&gt; — the infrastructure gap that makes agent-first systems hard.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>agenticdevelopment</category>
      <category>devex</category>
      <category>security</category>
    </item>
    <item>
      <title>I Answer Questions. AI Writes the Blog.</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 16 Jun 2026 03:56:41 +0000</pubDate>
      <link>https://dev.to/htekdev/i-answer-questions-ai-writes-the-blog-5h4e</link>
      <guid>https://dev.to/htekdev/i-answer-questions-ai-writes-the-blog-5h4e</guid>
      <description>&lt;p&gt;I finished building my blog pipeline on a Sunday. By April 21, &lt;a href="https://www.seroundtable.com/google-commodity-content-41200.html" rel="noopener noreferrer"&gt;Danny Sullivan was on stage at Google Search Central Live in Toronto&lt;/a&gt; telling the world exactly what I'd accidentally built.&lt;/p&gt;

&lt;p&gt;That's not prediction. That's arriving at the same conclusion from two different directions — and that convergence is the most interesting signal in the AI content era right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's the setup:&lt;/strong&gt; I run &lt;a href="https://htek.dev" rel="noopener noreferrer"&gt;htek.dev&lt;/a&gt;, a developer blog I publish using GitHub Copilot CLI agents, Astro 5, MDX, and Vercel. The old publishing workflow — open a text editor, grind out 1,500 words — felt misaligned with how I actually think. Ideas don't live in structured outlines. They live in car rides, in the middle of a workout, in the moment when two unrelated things click. I'd built complex &lt;a href="https://htek.dev/articles/copilot-home-assistant-ai-runs-my-household/" rel="noopener noreferrer"&gt;multi-agent systems to run my household&lt;/a&gt;, spec client sites, and govern 50+ AI agents across a production harness. My blog pipeline was still a blank document and a blinking cursor.&lt;/p&gt;

&lt;p&gt;So I built a pipeline. Five stages, each owned by a dedicated AI agent: &lt;code&gt;blog-planner&lt;/code&gt;, &lt;code&gt;blog-writer&lt;/code&gt;, &lt;code&gt;blog-reviewer&lt;/code&gt;, &lt;code&gt;content-illustrator&lt;/code&gt;, and a publishing orchestrator that handles GitHub, Vercel, DEV.to, Hashnode, and Medium. The pipeline runs from voice note to published article without me writing a single sentence.&lt;/p&gt;

&lt;p&gt;Then Google named the problem I'd solved before I knew I was solving it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Commodity Content Actually Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Google's definition is direct: commodity content is anything that "could originate from anyone" and adds little unique insight. Non-commodity content is unique, specific, and authentic — rooted in first-hand expertise others can't replicate. Danny Sullivan introduced this at Google Search Central Live Toronto, April 2026, and Google formalized it in their &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;AI optimization guide&lt;/a&gt; on May 15, 2026.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The running store example Sullivan used is worth sitting with. Commodity content: "Top 10 Things to Consider When Buying Running Shoes." Non-commodity content: "Why This Customer's Shoes Collapsed After 400 Miles: A Wear Pattern Analysis." One post exists on a hundred websites. The other can only exist if you examined the actual shoes.&lt;/p&gt;

&lt;p&gt;The distinction isn't about AI versus human authorship. It's about &lt;em&gt;replaceability&lt;/em&gt;. As &lt;a href="https://nikki-pilkington.com/commodity-vs-non-commodity-content-what-it-means-and-how-to-fix-yours/" rel="noopener noreferrer"&gt;Nikki Pilkington observed after the Toronto event&lt;/a&gt;: "Commodity content is interchangeable by definition. Non-commodity content exists nowhere else, because it comes from something that only happened to you."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.annsmarty.com/p/non-commodity-content-what-seos-and" rel="noopener noreferrer"&gt;Ann Smarty put it precisely&lt;/a&gt; in May 2026: commodity content is anything that can be "repackaged into an AI answer." If Google AI Overviews or ChatGPT can summarize your post without needing &lt;em&gt;you&lt;/em&gt; specifically — the content doesn't have a moat.&lt;/p&gt;

&lt;p&gt;That's the problem my pipeline was already solving, for reasons that had nothing to do with SEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline and Why the Interview Is the Crux
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The htek.dev blog pipeline runs five stages: idea generation, interview-driven capture, planner synthesis, blog-writer drafting, and review + illustration. Every stage is automated except one — the human interview, which is the only place my actual thinking enters the system. That's by design.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Idea generation is ambient and on-demand. I can say "I want to write about MeshWire" and the &lt;code&gt;blog-planner&lt;/code&gt; agent creates a structured idea with a topic gap analysis. Or I just say "interview me on any pending ideas" — and it does. Ideas arrive without me pausing whatever I'm doing to develop them. I throw the thought out there. The pipeline catches it and holds it until I'm ready.&lt;/p&gt;

&lt;p&gt;Once an idea matures into a brief, the planner writes interview questions — the kind that probe &lt;em&gt;how&lt;/em&gt; I think, not just what I know. Not "explain MeshWire" but "why does the current approach of connecting agents one by one fail, and what did you see break that made you build something different?" I answer when I have a free moment.&lt;/p&gt;

&lt;p&gt;The drive to the gym. A 12-minute commute. This week: the articles for &lt;a href="https://htek.dev/articles/stop-connecting-your-agents-one-by-one/" rel="noopener noreferrer"&gt;MeshWire's agent-mesh architecture&lt;/a&gt;, the &lt;a href="https://htek.dev/articles/stop-building-fat-copilot-extensions/" rel="noopener noreferrer"&gt;Fat Copilot Extensions pattern&lt;/a&gt;, and a raw-CDP performance deep-dive were all interviewed the same week — most of them captured in voice notes during a single gym session. Three blog-ready briefs by 10 AM on a Monday.&lt;/p&gt;

&lt;p&gt;The captured interview is the only non-automatable step. The planner synthesizes my answers into a brief: thesis, angle, voice anchors, must-avoid patterns. The &lt;code&gt;blog-writer&lt;/code&gt; agent works from that brief — first-person, opinionated, with my actual words and metaphors, cross-referencing related htek.dev articles, citing real sources, passing a dual Claude Opus 4.6 + GPT-5.5 review before any PR is created. The &lt;code&gt;content-illustrator&lt;/code&gt; agent generates the hero image and inline visuals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmyum1epn98jeh48jed6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgmyum1epn98jeh48jed6.webp" alt="The htek.dev blog pipeline: 6 stages from idea gen to publish. Interview Capture (cyan) is the only human-only step." width="798" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The article that comes out the other end is grounded in reasoning that only I produced. That's not a quality claim. It's a structural claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Author Bios and AI Disclaimers Don't Work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Adding an author bio, slapping on "written with AI assistance" disclaimers, or reverse-engineering E-E-A-T signals are all fixes applied to the deliverable. They don't change what the deliverable contains — which is still commodity output produced by a commodity workflow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've written about this pattern before in the context of agentic development: &lt;a href="https://htek.dev/articles/workflows-are-the-new-commodity/" rel="noopener noreferrer"&gt;code is no longer the asset, workflows are&lt;/a&gt;. The same principle holds for content. The blog post isn't the asset. The process that produced it is. If your process produces content "anyone could write," your process is the problem — not the byline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mariadykstra.com/non-commodity-content-and-ai-visibility/" rel="noopener noreferrer"&gt;Maria Dykstra's April 2026 analysis&lt;/a&gt; of Sullivan's framing captures it precisely: AI lowered the production cost of generic content to near zero, so Google's quality bar moved up to compensate. Commodity content now competes with what any AI can produce in seconds. Commodity content now competes with what any AI tool can produce in seconds. Surface fixes add the appearance of authenticity without the underlying substance. One more Google update and you're back where you started — because you fixed the symptom, not the cause.&lt;/p&gt;

&lt;p&gt;The teams that survive are the ones who redesign around &lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;authentic context capture&lt;/a&gt; — who ask not "is my content good?" but "does my &lt;em&gt;process&lt;/em&gt; produce things that only I could have produced?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scalability of Human Knowledge
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is not an SEO tactic. It's the scalability of human knowledge — using AI to multiply the throughput of authentic human thinking, not to replace it.&lt;/strong&gt; The interview-driven pipeline works because of a fundamental mismatch: humans think in conversation, but are expected to produce content in document form.&lt;/p&gt;

&lt;p&gt;Give me a question that actually probes how I think and I'll generate more genuine insight in five minutes than I could produce in an hour staring at a blank editor. The problem with most content workflows is they force you to translate conversational thinking into structured prose before you've even captured it — which is where the authentic signal gets lost.&lt;/p&gt;

&lt;p&gt;What AI does in this pipeline is draw the lines and glue the edges. I answer interview questions the way I'd answer them to a colleague over lunch — direct, following the thought wherever it goes, making connections I didn't plan in advance. The &lt;code&gt;blog-writer&lt;/code&gt; agent structures that into a coherent article. What comes out is my actual reasoning, not a synthesis of what others have already published.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;non-commodity by Google's own definition&lt;/a&gt;: unique (my specific viewpoint), specific (a real pipeline, real tools, real timestamps from this Monday morning), authentic (first-hand knowledge no one else has). &lt;a href="https://www.practicalecommerce.com/googles-non-commodity-push-isnt-new" rel="noopener noreferrer"&gt;Practical Ecommerce noted in June 2026&lt;/a&gt; that this isn't a new idea — Google has emphasized experience-driven content for years under the E-E-A-T framework. What's new is AI making the cost of commodity content so low that the distinction has become existential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Meta-Loop Is the Point
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The article you're reading right now was produced by the pipeline it describes — voice note on a Monday morning drive, synthesized by the planner, drafted by the blog-writer agent, reviewed by Claude Opus 4.6 and GPT-5.5, illustrated, and submitted via PR. That's not a footnote. It's the proof.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article was produced by the pipeline it describes.&lt;/p&gt;

&lt;p&gt;This Monday morning I drove 12 minutes to the gym. I answered five interview questions into a voice note: about Google's commodity content framing, about why surface-level fixes fail, about what the pipeline actually feels like from the inside, and about what this article shouldn't say. The planner synthesized a brief. The blog-writer wrote from that brief. A dual Claude Opus 4.6 + GPT-5.5 review ran. The illustrator generated a hero image. A PR was created on GitHub, and I reviewed it before it merged.&lt;/p&gt;

&lt;p&gt;That meta-loop is intentional. The thing you're reading is exactly the kind of content the pipeline is designed to produce — and exactly the kind of content that &lt;a href="https://www.semrush.com/blog/google-publishes-generative-ai-search-guide/" rel="noopener noreferrer"&gt;Google's generative AI search guidance&lt;/a&gt; says should survive the shift.&lt;/p&gt;

&lt;p&gt;The side comment from that voice note session is worth capturing: the same interview-driven pattern is emerging across other surfaces — client proposals, video editing scripts, social posts. The pipeline architecture isn't specific to blog articles. Anywhere humans are expected to produce structured output from unstructured expertise, the interview-first approach is the right architecture. That's a separate article. It's already in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Refactoring
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Google AI era didn't change what makes content worth reading. It made the cost of producing worthless content so low that you can't compete on production speed anymore — you have to compete on something AI can't replicate in bulk. That's authentic expertise, captured systematically in a workflow built around human thinking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not prescribing this pipeline. This is not a how-to.&lt;/p&gt;

&lt;p&gt;The point is: the Google AI era didn't change what makes content worth reading. It made the cost of producing &lt;em&gt;worthless&lt;/em&gt; content so low that you can't compete on production speed anymore. You have to compete on something AI can't replicate in bulk — authentic expertise, captured systematically.&lt;/p&gt;

&lt;p&gt;Look at what your content workflow produces today. Ask whether it could have come from anyone. If the answer is yes, you already know what to refactor. The question isn't "is my content good?" It's "does my &lt;em&gt;process&lt;/em&gt; produce things that only I could have produced?"&lt;/p&gt;

&lt;p&gt;That's the question I was already asking when I built this pipeline. &lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;Google arriving at the same answer&lt;/a&gt; — independently, on a different timeline, from a completely different direction — just confirms what happens when you're solving the right problem.&lt;/p&gt;

&lt;p&gt;Start where the workflow is. The content will follow.&lt;/p&gt;

</description>
      <category>github</category>
      <category>agenticdevelopment</category>
      <category>aiagents</category>
      <category>automation</category>
    </item>
    <item>
      <title>Stop Connecting Your Agents One by One</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Fri, 12 Jun 2026 02:42:55 +0000</pubDate>
      <link>https://dev.to/htekdev/stop-connecting-your-agents-one-by-one-2aof</link>
      <guid>https://dev.to/htekdev/stop-connecting-your-agents-one-by-one-2aof</guid>
      <description>&lt;h2&gt;
  
  
  I had two agents and they couldn't talk to each other
&lt;/h2&gt;

&lt;p&gt;I had a work agent running. Access to my Microsoft context, work tools, work calendar.&lt;/p&gt;

&lt;p&gt;I had a personal agent running. Access to my family stuff, the home assistant, my personal calendar, the household systems.&lt;/p&gt;

&lt;p&gt;Different contexts. Different tools. Two completely separate workspaces, on purpose. The last thing I wanted was my personal agent firing off an email on behalf of my work — or my work agent poking at my family calendar.&lt;/p&gt;

&lt;p&gt;But I did want them to coordinate. If a personal commitment landed at 2 PM on a Tuesday, the work agent should know to keep that time blocked. If a video pipeline at home finished a render, the work side shouldn't be the one sending the notification — but it should be aware. The boundary needed to stay sharp. The communication needed to exist anyway.&lt;/p&gt;

&lt;p&gt;So I wrote a tiny extension. A local SQLite database, a few CLI commands, and just enough plumbing for two &lt;a href="https://docs.github.com/en/copilot/github-copilot-in-the-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt; sessions to drop messages into a shared queue. I called it &lt;a href="https://htek.dev/articles/agent-mesh-cross-session-communication-copilot-cli/" rel="noopener noreferrer"&gt;Agent Mesh&lt;/a&gt; and wrote about it back in May. It got more attention than I expected.&lt;/p&gt;

&lt;p&gt;That little extension is the seed of what I want to talk about today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l9q18wr4bx61ypl2fsp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l9q18wr4bx61ypl2fsp.webp" alt="Two isolated agents — before and after MeshWire wires them together while keeping their contexts separate" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Before MeshWire: two isolated agent islands with no coordination. After: wired together through the mesh — boundaries intact, communication flowing.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Today I'm opening the public beta of MeshWire
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://meshwire.io" rel="noopener noreferrer"&gt;MeshWire&lt;/a&gt; is the next version of that idea, taken seriously. It's a hosted messaging layer for multi-agent systems — &lt;code&gt;npm install meshwire&lt;/code&gt;, sign in, get a token, and your agents can find each other and exchange messages across processes, machines, and harnesses.&lt;/p&gt;

&lt;p&gt;The npm package is live (&lt;a href="https://www.npmjs.com/package/meshwire" rel="noopener noreferrer"&gt;&lt;code&gt;meshwire@0.1.8&lt;/code&gt;&lt;/a&gt;) and the site is up at &lt;a href="https://meshwire.io" rel="noopener noreferrer"&gt;meshwire.io&lt;/a&gt;. It's free during the public beta. I'm not selling anything yet — I just want feedback.&lt;/p&gt;

&lt;p&gt;But I want to spend most of this article on &lt;em&gt;why&lt;/em&gt; MeshWire exists, because that's the part the AI tooling space keeps getting wrong.&lt;/p&gt;
&lt;h2&gt;
  
  
  The harness landscape is not a competition
&lt;/h2&gt;

&lt;p&gt;If you've been paying attention to the AI coding tools space for the last twelve months, you've noticed something: every harness has a personality. I track this constantly in &lt;a href="https://htek.dev/articles/all-agent-harnesses-live-comparison/" rel="noopener noreferrer"&gt;my live agent harness comparison&lt;/a&gt;, and the more I update that page, the more obvious the pattern becomes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; is biased toward GitHub. It's deeply integrated with PRs, Actions, and the developer's existing repo workflow. It's where my code lives.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; is optimized for Anthropic's models. The harness is tuned to the way Claude reasons.&lt;/li&gt;
&lt;li&gt;Pi (the agent harness, not the math constant) is built for customizability — you can bend it to almost any shape.&lt;/li&gt;
&lt;li&gt;Hermes-style harnesses lean into continuous learning loops.&lt;/li&gt;
&lt;li&gt;OpenClaw and the open-source crowd are exploring different architectures entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole space wants to frame this as a winner-take-all bake-off. &lt;em&gt;Which AI tool is best?&lt;/em&gt; &lt;em&gt;Which IDE will dominate?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I think that framing is wrong. Each of these harnesses is built around a different specialization — different model partners, different runtime assumptions, different surfaces. Copilot is where I do almost all of my work; it's the most complete loop in the industry for getting from idea to merged PR, and it's where my own platform is built. The others exist because the space is genuinely big enough for more than one shape. They aren't substitutes — they're specializations, and most serious teams I talk to end up with more than one running somewhere.&lt;/p&gt;

&lt;p&gt;If they're specializations, then the question stops being "which one wins" and starts being "how do they cooperate?"&lt;/p&gt;
&lt;h2&gt;
  
  
  And it's not just agents — it's interfaces
&lt;/h2&gt;

&lt;p&gt;The other piece people keep missing: this isn't only about agents talking to other agents. The mesh has to include the &lt;em&gt;surfaces&lt;/em&gt; humans are already using.&lt;/p&gt;

&lt;p&gt;Right now, every developer who wants their AI agent to text them is wiring up a Telegram bot manually. Every developer who wants Slack notifications is wiring up Slack. Same for Teams. Same for SMS. We've all duplicated the same five integrations in our own private repos, with our own private credentials, talking to our own private agents.&lt;/p&gt;

&lt;p&gt;That's fundamentally outdated.&lt;/p&gt;

&lt;p&gt;The interfaces — Telegram, Teams, Slack, email, SMS — should themselves be participants in the mesh. An agent shouldn't ship a Telegram driver. It should send a message to "the Telegram surface" and let the mesh route it. Same agent code, no per-channel rewrite. When I add Discord later, no agent has to change.&lt;/p&gt;

&lt;p&gt;That's the model. Agents are nodes. Interfaces are nodes. Data sources are nodes. The mesh is the wire.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F011m95tj045wbbewytq6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F011m95tj045wbbewytq6.webp" alt="MeshWire architecture: agent harnesses and communication interfaces connected as equal mesh participants through a shared messaging layer with thin adapter shims" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MeshWire treats every harness and interface as an equal mesh participant — connected through a shared messaging layer via thin adapters. The SDK is the stable contract; adapters translate between harness formats.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How MeshWire actually works
&lt;/h2&gt;

&lt;p&gt;The shape is deliberately boring, because boring infrastructure is what wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A messaging service.&lt;/strong&gt; A small SDK. The SDK exposes the operations you'd expect — &lt;code&gt;sendMessage&lt;/code&gt;, &lt;code&gt;replyToMessage&lt;/code&gt;, &lt;code&gt;getAgents&lt;/code&gt;, &lt;code&gt;receiveMessage&lt;/code&gt; — and a hosted backend handles persistence and delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An adapter pattern.&lt;/strong&gt; The Copilot extension is the first adapter. It's intentionally a thin shim — it translates Copilot's tool-invocation format into the MeshWire SDK calls and gets out of the way. The heavy logic lives in the SDK, not the adapter.&lt;/p&gt;

&lt;p&gt;That matters because it means the next adapter — for Claude Code, or Hermes, or whatever harness shows up next quarter — is also a thin shim, not a rewrite. If your agent logic is built on the SDK, the same agent runs on any harness that has a MeshWire adapter. Portability and testability are the real wins; cross-harness messaging is the headline feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb51g4ury93bxhjn7d51.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb51g4ury93bxhjn7d51.webp" alt="The thin-shim adapter pattern: harnesses at the top, thin adapters in the middle, the MeshWire SDK as the stable contract at the bottom — same agent logic runs on any harness" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The adapter is intentionally thin — it translates a harness's invocation format into SDK calls and gets out of the way. Your agent logic lives in the SDK layer and gains portability across any harness with an adapter.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local mode is on the roadmap.&lt;/strong&gt; A lot of developers — me included — don't want their agent traffic going through a cloud they don't operate. The plan is to swap the hosted HTTP/DynamoDB backend for a local SQLite store so the same SDK runs fully offline. Same code, same calls, no network. That's not in the public beta yet, but it's the next big rock.&lt;/p&gt;

&lt;p&gt;If you've read &lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;my piece on Harness as Code&lt;/a&gt;, this is the same instinct: stop hand-rolling glue, define the interface, let the runtime swap underneath.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I'm shipping this for free, and why I'm not pretending otherwise
&lt;/h2&gt;

&lt;p&gt;I'll be honest with you: I have no idea what the demand for this is.&lt;/p&gt;

&lt;p&gt;I built MeshWire because I needed it. The work-agent-and-personal-agent problem was real. The duplicated-Telegram-integration problem was real. Agent Mesh was a workable hack; MeshWire is what it should look like once you take it seriously.&lt;/p&gt;

&lt;p&gt;I don't know how to charge for it yet. I don't have a pricing hypothesis. I don't have a five-stage adoption funnel. What I have is an open beta, a working npm package, and an honest ask: if any part of this resonates, please use it and tell me what's missing.&lt;/p&gt;

&lt;p&gt;The first external user signed up the same day I posted about it internally on Microsoft Teams — Cole Flenniken, a friend at Microsoft, saw the post and wired in. That's a sample size of one, in my own network. It's not a market signal, and I'm not going to pretend it is. But it was a real human caring enough to try the thing, and that's the only validation I'm chasing right now: real humans, real use, real feedback.&lt;/p&gt;

&lt;p&gt;If you've read the &lt;a href="https://htek.dev/articles/agent-harnesses-controlling-ai-agents-2026/" rel="noopener noreferrer"&gt;organizational singularity&lt;/a&gt; thread of work I've been doing — agents with passports, identities, cross-harness interactions — MeshWire is the wire underneath that vision. It's the boring transport layer that has to exist before any of the more interesting cross-org agent behavior is even possible.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it, break it, tell me what's wrong
&lt;/h2&gt;

&lt;p&gt;If you have agents running in more than one place — Copilot, Claude Code, a home automation script, a Telegram bot, anything — and you've felt the friction of them being islands, please grab the beta:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Sign in at &lt;a href="https://meshwire.io" rel="noopener noreferrer"&gt;meshwire.io&lt;/a&gt;, get your token, and wire your first two agents together. The whole point is to see what people actually do with a mesh once they have one. I've also been &lt;a href="https://htek.dev/articles/work-life-calendar-sync-agent-mesh/" rel="noopener noreferrer"&gt;syncing my own work and personal calendars through agent-mesh&lt;/a&gt;, so I'll be dogfooding the migration to MeshWire publicly.&lt;/p&gt;

&lt;p&gt;Send me what breaks. Send me what's missing. Send me the use case I haven't thought of. That's the entire ask.&lt;/p&gt;

&lt;p&gt;The harnesses aren't competing. They never were. The only thing missing was a wire.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://meshwire.io" rel="noopener noreferrer"&gt;MeshWire — meshwire.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/meshwire" rel="noopener noreferrer"&gt;&lt;code&gt;meshwire&lt;/code&gt; on npm (v0.1.8)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/agent-mesh-cross-session-communication-copilot-cli/" rel="noopener noreferrer"&gt;Agent Mesh: cross-session communication for Copilot CLI&lt;/a&gt; — the predecessor extension&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/all-agent-harnesses-live-comparison/" rel="noopener noreferrer"&gt;All Agent Harnesses: The Live Comparison&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/agent-harnesses-controlling-ai-agents-2026/" rel="noopener noreferrer"&gt;Agent Harnesses: Controlling AI Agents in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;What Is Harness as Code?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htek.dev/articles/work-life-calendar-sync-agent-mesh/" rel="noopener noreferrer"&gt;Work-Life Calendar Sync via Agent Mesh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/github-copilot-in-the-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aiagents</category>
      <category>agenticdevelopment</category>
      <category>modelcontextprotocol</category>
      <category>opensource</category>
    </item>
    <item>
      <title>When GitHub Copilot Extensions Go Wrong — Part 1</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Fri, 12 Jun 2026 02:41:39 +0000</pubDate>
      <link>https://dev.to/htekdev/when-github-copilot-extensions-go-wrong-part-1-15hc</link>
      <guid>https://dev.to/htekdev/when-github-copilot-extensions-go-wrong-part-1-15hc</guid>
      <description>&lt;p&gt;It took me 40 minutes to figure out why all 43 of my Copilot CLI agents were frozen. No errors. No crashes. Just silence — every agent, every cron job, every background task completely unresponsive. I had shipped a new Copilot CLI extension that afternoon. It had one unclosed &lt;code&gt;async&lt;/code&gt; operation in a GitHub API polling loop, no timeout guard, no &lt;code&gt;catch&lt;/code&gt; block. That was enough to stall the entire Node.js event loop in the extension host process. Every tool handler across every registered extension — dead.&lt;/p&gt;

&lt;p&gt;I fixed the immediate issue in about 10 minutes once I found it. Then I spent the next three weeks trying to understand &lt;em&gt;why this happened at all&lt;/em&gt;, and whether there was an architecture that could have prevented it.&lt;/p&gt;

&lt;p&gt;This is Part 1 of what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes an Extension "Fat"
&lt;/h2&gt;

&lt;p&gt;A fat Copilot CLI extension is one that bundles business logic directly inside its handler functions — inline HTTP calls, LLM chains, stateful caches, database writes, async operations with no timeout guards. The extension registers tools, hooks, and MCP connections, but then &lt;em&gt;also implements everything they do&lt;/em&gt; in the same file, sometimes the same function.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice:&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;// fat-extension.mjs — what NOT to do&lt;/span&gt;

&lt;span class="c1"&gt;// Fat pattern: business logic inlined directly inside handlers — no isolation&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;joinSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analyze_pr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze a GitHub pull request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner/repo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PR number&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;required&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="s2"&gt;repo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pr&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;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pr&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="c1"&gt;// Inline GitHub API call — no timeout guard&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.github.com/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pulls/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pr&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Inline LLM call — can hang indefinitely&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Analyze: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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="c1"&gt;// Inline DB write — no error boundary&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pr_analysis&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="nx"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run_ci_check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run CI check on a branch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Branch name&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;required&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="s2"&gt;branch&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;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;branch&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="c1"&gt;// 80 more lines of inline logic...&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onPreToolUse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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="c1"&gt;// 120 more lines of inline validation...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem isn't the code quality — it's the &lt;em&gt;architecture&lt;/em&gt;. Every handler is an async operation running directly inside the extension host process. &lt;a href="https://docs.github.com/en/copilot/building-copilot-extensions/about-building-copilot-extensions" rel="noopener noreferrer"&gt;GitHub Copilot CLI extensions&lt;/a&gt; share that process. If &lt;code&gt;analyze-pr&lt;/code&gt; hangs on an API call that never times out, the entire event loop stalls. Tools from &lt;em&gt;other&lt;/em&gt; extensions stop responding. Your agents sit there waiting for tools that will never answer.&lt;/p&gt;

&lt;p&gt;I built this pattern three times before I understood why it kept breaking. The first iteration had no timeouts. The second had timeouts but inline state. The third had everything right &lt;em&gt;except&lt;/em&gt; the unhandled rejection in the GitHub polling loop that eventually took down the fleet.&lt;/p&gt;

&lt;p&gt;The real fix wasn't a better &lt;code&gt;try/catch&lt;/code&gt;. It was a different architecture entirely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77fm5oln7izvng8c3y90.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77fm5oln7izvng8c3y90.webp" alt="Side-by-side architecture comparison of fat extension anti-pattern vs hollow extension pattern. Left: fat extension with inline HTTP calls, LLM chains, and no timeout guards causing event loop stall. Right: hollow extension delegating all logic to an injectable Factory SDK." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Fat Extension vs Hollow Extension — how embedding logic inside the extension host leads to fleet-wide failure, and how the hollow pattern prevents it&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Node.js Event Loop Is Not a Safety Net
&lt;/h2&gt;

&lt;p&gt;The extension host runs your tool and hook handlers in series within each invocation context. An awaited operation that never resolves — a hung API call, a Promise that's never settled, an infinite polling loop — keeps the handler alive indefinitely. &lt;a href="https://nodejs.org/api/process.html#event-unhandledrejection" rel="noopener noreferrer"&gt;Node.js fires an &lt;code&gt;unhandledRejection&lt;/code&gt; event&lt;/a&gt; when a rejected Promise has no handler, but the more dangerous failure mode is a Promise that never rejects — it just hangs. Any subsequent call that needs a response from that handler waits forever.&lt;/p&gt;

&lt;p&gt;In my experience running 40+ Copilot CLI agents against the same extension host, one stalled handler propagates outward fast. Tools from other extensions stop responding as the dispatch queue fills with unanswered requests. &lt;a href="https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick" rel="noopener noreferrer"&gt;Node.js event loop semantics&lt;/a&gt; mean a microtask queue backed up with unresolved Promises doesn't stop other I/O — but it does mean every caller waiting on those unresolved Promises will time out or freeze instead of getting a response.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Hollow Extension Pattern — An Idea in Progress
&lt;/h2&gt;

&lt;p&gt;After the fleet went down, I started sketching. What if a Copilot CLI extension &lt;em&gt;never&lt;/em&gt; contained any business logic at all? What if the entire extension was just a registration surface — calling methods on an injectable factory, wiring the results into the harness, and that was it?&lt;/p&gt;

&lt;p&gt;The hollow extension pattern treats a Copilot CLI extension as a &lt;em&gt;registration surface only&lt;/em&gt;. The extension's entire job is to wire an injectable factory into the harness — nothing more.&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;// hollow-extension.mjs — the pattern that works&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;PRAnalyzerFactory&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="s2"&gt;./factory.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// all logic lives here&lt;/span&gt;

&lt;span class="c1"&gt;// Configure the factory — zero business logic in the extension itself&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PRAnalyzerFactory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retries&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="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] failed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Extension is pure registration — no inline handlers&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;joinSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTools&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// returns Tool[] array&lt;/span&gt;
  &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHooks&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// returns { onPreToolUse, onPostToolUse, onSessionStart }&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the complete extension. Twenty-something lines. No inline business logic. No async footguns. No state.&lt;/p&gt;

&lt;p&gt;I wasn't confident this would work. On paper it felt too simple, too thin to actually prevent a fleet-wide outage. But I tested it. The tools responded. The agents answered. The fleet came back online. I realized: sometimes you don't fix a reliability problem by adding controls. You fix it by removing surfaces where things can break.&lt;/p&gt;

&lt;p&gt;The extension doesn't know what &lt;code&gt;factory.getTools()&lt;/code&gt; returns internally. It doesn't know how the &lt;code&gt;analyze-pr&lt;/code&gt; tool handles its GitHub API call, how it manages timeouts, or whether it batches requests. It just registers whatever the factory provides and starts the Copilot CLI extension host.&lt;/p&gt;

&lt;p&gt;This is the &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection" rel="noopener noreferrer"&gt;dependency injection principle&lt;/a&gt; applied to extension architecture — and it's the same pattern I described in &lt;a href="https://htek.dev/articles/three-layers-your-ai-agent-is-missing/" rel="noopener noreferrer"&gt;the three architectural layers every AI agent is missing&lt;/a&gt;. The extension is the registration layer. The factory is the logic layer. They're separate, and the separation is the safety mechanism.&lt;/p&gt;

&lt;p&gt;The pattern is also a direct application of the &lt;a href="https://refactoring.guru/design-patterns/factory-method" rel="noopener noreferrer"&gt;factory method&lt;/a&gt; design pattern — a 30-year-old idea that turns out to be exactly what modern extension architectures need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Factory Implementer SDKs
&lt;/h2&gt;

&lt;p&gt;Once the hollow extension pattern was clear — register the contract, implement nothing — one question followed immediately: &lt;em&gt;what fulfills the contract?&lt;/em&gt; That’s the moment it clicked. &lt;em&gt;“Oh my God, I just thought of something — we can just CREATE what I just said.”&lt;/em&gt; The extension is describing a factory interface. So build the factory. That’s the entire factory SDK idea in one sentence.&lt;/p&gt;

&lt;p&gt;The factory SDK is where all the real work happens — but it happens in isolation, behind a well-defined interface.&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;// factory.mjs — logic lives here, not in the extension&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PRAnalyzerFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// this.github, this.analyzer, this.ci, this.validator are injected deps&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;getTools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Returns the Tool[] array that joinSession expects&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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="s2"&gt;analyze_pr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze a GitHub pull request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&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;required&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="s2"&gt;repo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pr&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;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;withTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pr&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&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="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run_ci_check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run a CI check on a branch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;required&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="s2"&gt;branch&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;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;withTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;branch&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&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="nf"&gt;getHooks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Returns the hooks object that joinSession expects&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;onSessionStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &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;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[pr-analyzer] Factory extension active.&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;onPreToolUse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preToolUseHook&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;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb43dj0sctzplzxajob8b.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb43dj0sctzplzxajob8b.webp" alt="Factory SDK dependency injection flow diagram. Shows HarnessFactory implementing ToolProvider, HookProvider, and MCPProvider interfaces. Injected dependencies (github, analyzer, ci, validator) flow into the factory, which wraps every handler with withTimeout and withRetry guards before returning clean tool/hook/MCP contracts to the hollow extension." width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Factory SDK Dependency Injection Flow — injected deps in, guarded contracts out. All logic owned by the factory, all registration owned by the extension.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every tool is wrapped in &lt;code&gt;withTimeout&lt;/code&gt; and optionally &lt;code&gt;withRetry&lt;/code&gt;. The &lt;code&gt;this.github&lt;/code&gt;, &lt;code&gt;this.analyzer&lt;/code&gt;, &lt;code&gt;this.ci&lt;/code&gt;, and &lt;code&gt;this.validator&lt;/code&gt; dependencies are injected at factory construction — swappable, mockable, testable.&lt;/p&gt;

&lt;p&gt;The factory approach also unlocks something I hadn't anticipated: I can now unit test all my tool logic &lt;em&gt;without a running Copilot CLI session&lt;/em&gt;. I instantiate &lt;code&gt;HarnessFactory&lt;/code&gt; with mock dependencies and test the handlers directly. The extension is just the deployment wrapper — the factory is the software.&lt;/p&gt;

&lt;p&gt;This mirrors what I wrote about in &lt;a href="https://htek.dev/articles/what-is-harness-as-code/" rel="noopener noreferrer"&gt;What Is Harness as Code&lt;/a&gt;: declarative, injectable, reproducible. The fat extension anti-pattern is the same mistake as the &lt;a href="https://htek.dev/articles/your-god-prompt-is-the-new-monolith/" rel="noopener noreferrer"&gt;god prompt monolith&lt;/a&gt; — everything bundled in one place because it was faster to write that way, slower to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Unlocks for the Extension Ecosystem
&lt;/h2&gt;

&lt;p&gt;The hollow extension pattern makes extensions into &lt;em&gt;interface specifications&lt;/em&gt; rather than monolithic bundles. Teams can build multiple factory SDK implementations against the same extension interface — swapping auth strategies, retry policies, or MCP connections without touching the extension registration layer. This is the composability model that makes extension marketplaces viable.&lt;/p&gt;

&lt;p&gt;Here's what got me excited beyond the immediate reliability win: this pattern is the right foundation for a Copilot extension marketplace.&lt;/p&gt;

&lt;p&gt;Right now, if you want to adopt someone else's Copilot CLI extension, you're installing their full implementation — their API keys, their error handling assumptions, their retry logic, their specific GitHub API version. You're accepting the whole fat extension as-is. The &lt;a href="https://cli.github.com/manual/gh_extension_install" rel="noopener noreferrer"&gt;gh extension install&lt;/a&gt; command is a blunt instrument for this reason: you get the whole package, hardcoded decisions and all.&lt;/p&gt;

&lt;p&gt;With the hollow extension model, extensions become &lt;em&gt;interface specifications&lt;/em&gt;, not implementations. The extension publishes what tools and hooks it registers, and what interfaces the factory implementer must satisfy. Teams can build their own factory SDKs against those interfaces — using their own auth patterns, their own retry strategies, their own MCP connections. The &lt;a href="https://www.typescriptlang.org/docs/handbook/2/objects.html" rel="noopener noreferrer"&gt;TypeScript interface system&lt;/a&gt; is the natural contract layer here: publish the interface, version it separately from the implementation.&lt;/p&gt;

&lt;p&gt;The Copilot extension platform already has the extensibility primitives to support this. Tools, hooks, and MCP connections are already first-class. The hollow extension + factory SDK separation is a pattern any extension builder can adopt today — no platform changes required.&lt;/p&gt;

&lt;p&gt;I've written about the &lt;a href="https://htek.dev/articles/agentic-development-maturity-curve/" rel="noopener noreferrer"&gt;agentic development maturity curve&lt;/a&gt; before: at expert level, complexity collapses back to simple, explicit primitives. Fat extensions are the middle of that curve — impressive-looking, fragile. Hollow extensions are what you build when you've learned what actually goes wrong at 3 AM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;The hollow extension pattern solved the fleet stability crisis. But it raised a new question: if the extension is just a registration surface, what about the factory SDK itself? How do you scale that? How do you compose multiple factory implementations? What happens when you have &lt;em&gt;too many&lt;/em&gt; factories, too many injectable dependencies, too many layers?&lt;/p&gt;

&lt;p&gt;I've been experimenting with an answer — a framework I've been calling "Harness as Code." It's the next iteration of the hollow pattern idea, and it changes how you think about building modular Copilot ecosystems.&lt;/p&gt;

&lt;p&gt;That's Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern in Three Sentences
&lt;/h2&gt;

&lt;p&gt;Register thin. Inject logic. Guard every async.&lt;/p&gt;

&lt;p&gt;The line that crystallized it: &lt;em&gt;"Not the files, the factory. Not the context, the mechanism."&lt;/em&gt; Every time I was chasing an extension bug, I was looking in the wrong layer. The extension is a file — inert, structural, just registration. The factory is the mechanism — where reliability lives, where tests run, where logic can be replaced without touching the extension surface. Fix the mechanism. Don't touch the file.&lt;/p&gt;

&lt;p&gt;An extension's job is to tell the Copilot CLI harness what's available — not to &lt;em&gt;be&lt;/em&gt; what's available. The business logic belongs in a factory SDK that owns its own timeout boundaries, error surfaces, and dependency graph. One bad extension shouldn't be able to take down your fleet. With the hollow pattern, it can't.&lt;/p&gt;

&lt;p&gt;If you're building for the &lt;a href="https://docs.github.com/en/copilot/github-copilot-in-the-cli/using-github-copilot-in-the-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt; ecosystem, this is the pattern I've landed on. Whether it stays this way, or whether Harness as Code evolves it further, I'm still learning. But the principle holds: don't embed logic in extensions. Separate registration from implementation. Guard every async boundary. That's the foundation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Related: &lt;a href="https://htek.dev/articles/copilot-cli-self-restart-extension/" rel="noopener noreferrer"&gt;I Taught My AI Agent to Restart Itself&lt;/a&gt; — another extension architecture lesson learned the hard way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>copilotcli</category>
      <category>agenticdevelopment</category>
      <category>devex</category>
    </item>
    <item>
      <title>I Replaced Playwright With Raw CDP</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Thu, 11 Jun 2026 11:27:32 +0000</pubDate>
      <link>https://dev.to/htekdev/i-replaced-playwright-with-raw-cdp-2f7n</link>
      <guid>https://dev.to/htekdev/i-replaced-playwright-with-raw-cdp-2f7n</guid>
      <description>&lt;h2&gt;
  
  
  The Agent Made a Better Call Than I Would Have
&lt;/h2&gt;

&lt;p&gt;I was building a responsive design testing pipeline for &lt;a href="https://htek.dev/articles/two-client-sites-three-days-agentive-context-engineering/" rel="noopener noreferrer"&gt;a client project&lt;/a&gt;. The goal was simple: capture screenshots of every page section at 11 viewport sizes, feed them to an AI vision model, get a structured report of what's broken.&lt;/p&gt;

&lt;p&gt;I handed the task to an agent and expected Playwright. It's the obvious choice — well-documented, clean API, every tutorial defaults to it. The agent had a different idea.&lt;/p&gt;

&lt;p&gt;It reached for raw &lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;Chrome DevTools Protocol&lt;/a&gt; over WebSocket. No Playwright, no Puppeteer — just JSON-RPC messages sent directly to Chrome. When I dug into why, the answer was immediate: Playwright was failing to resize the browser window correctly at certain viewport dimensions. Direct &lt;code&gt;Emulation.setDeviceMetricsOverride&lt;/code&gt; via CDP handled it cleanly. No abstraction layer fighting against you. Just a direct instruction to the browser.&lt;/p&gt;

&lt;p&gt;I kept it.&lt;/p&gt;

&lt;p&gt;That wasn't even the interesting part. What the agent built next — the approach it invented for getting AI to analyze multiple screenshots — turned out to be a general pattern I hadn't encountered before. I've started calling it &lt;strong&gt;compaction&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Manual responsive testing is one of those things that sounds manageable until you try to do it systematically. Eleven viewport sizes across a multi-section page with a password gate. That's potentially hundreds of screenshots. Reviewing them by hand isn't a workflow; it's a punishment.&lt;/p&gt;

&lt;p&gt;You could automate the comparison with perceptual diff tools like &lt;a href="https://www.chromatic.com/" rel="noopener noreferrer"&gt;Chromatic&lt;/a&gt; or &lt;a href="https://percy.io/" rel="noopener noreferrer"&gt;Percy&lt;/a&gt;, but those require baseline screenshots and tell you that something &lt;em&gt;changed&lt;/em&gt; — not whether the layout is actually correct. A broken layout you've never seen before passes as "no regression."&lt;/p&gt;

&lt;p&gt;What I wanted was something different: an AI that could look at a layout and say "this section is cropped at 390px, that column collapses wrong at 768px, this text is illegible on ultrawide." Natural language, structural, semantic feedback — not a pixel diff.&lt;/p&gt;

&lt;p&gt;The challenge was getting that feedback efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CDP and Not Playwright
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;Chrome DevTools Protocol&lt;/a&gt; is the actual wire protocol underneath Chrome-based browser automation. Playwright translates high-level method calls into CDP messages for Chromium. So does Puppeteer. Selenium's DevTools integration does the same.&lt;/p&gt;

&lt;p&gt;Going raw means connecting directly via WebSocket to a Chrome instance launched with &lt;code&gt;--remote-debugging-port&lt;/code&gt;, then firing JSON-RPC commands yourself:&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;// Connect to Chrome&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CDPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webSocketDebuggerUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Set viewport — direct, no Playwright wrapper&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Emulation.setDeviceMetricsOverride&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;844&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;deviceScaleFactor&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="na"&gt;mobile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;screenOrientation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;angle&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portraitPrimary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Capture screenshot&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page.captureScreenshot&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;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fromSurface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;captureBeyondViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependencies beyond Node.js 22+ (which has a stable built-in &lt;code&gt;WebSocket&lt;/code&gt; global). The tool has one npm dependency: &lt;a href="https://sharp.pixelplumbing.com/" rel="noopener noreferrer"&gt;&lt;code&gt;sharp&lt;/code&gt;&lt;/a&gt; for image compositing. Everything else is Node built-ins.&lt;/p&gt;

&lt;p&gt;There's something clarifying about working at this level. You stop debugging "why is Playwright doing X" and start reasoning directly about what Chrome is doing. When viewport resizing wasn't behaving, there was no abstraction to blame and nowhere to hide — which made the fix obvious.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fi-replaced-playwright-with-raw-cdp%2Fdiagram3-cdp-websocket-flow.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fi-replaced-playwright-with-raw-cdp%2Fdiagram3-cdp-websocket-flow.webp" alt="CDP WebSocket flow diagram: Node.js connects directly to Chrome via WebSocket, sending JSON-RPC commands like Emulation.setDeviceMetricsOverride and Page.captureScreenshot without a Playwright abstraction layer" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Raw CDP architecture: Node.js speaks JSON-RPC directly to Chrome — no Playwright in between, and only two npm dependencies: sharp and node:fs&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Compaction Insight
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting.&lt;/p&gt;

&lt;p&gt;The naive approach to AI visual validation is: one screenshot per viewport, one vision API call per screenshot, aggregate results. For 11 viewports across 8 sections, that's 88 API calls. That's slow, expensive, and you lose something important: the ability to compare layouts &lt;em&gt;side by side&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The agent built something smarter. For each page section, it composites all 11 viewport screenshots into a single labeled grid image using &lt;a href="https://sharp.pixelplumbing.com/" rel="noopener noreferrer"&gt;sharp&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│  section-02 (hero) · Homepage Hero                          │
├──────────────────┬──────────────────┬──────────────────┬───┤
│ iphone14-portrait│ android-360x800  │ ipad-portrait    │...│
│  390×844         │ 360×800          │ 768×1024         │   │
│ [screenshot]     │ [screenshot]     │ [screenshot]     │   │
└──────────────────┴──────────────────┴──────────────────┴───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each cell has a header strip showing the viewport slug and exact dimensions. The top banner shows the section ID and label. Everything the AI needs to orient itself is embedded in the image.&lt;/p&gt;

&lt;p&gt;One image. One vision call. 11 viewports analyzed together.&lt;/p&gt;

&lt;p&gt;That's the compaction. Instead of making the AI precise about pixel coordinates across dozens of separate images, you compact everything into a single reference frame where the labels &lt;em&gt;are&lt;/em&gt; the coordinates.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI can interpret an image through natural language, but it's hard to be precise about positioning. Compacting all the different views with text labels into one image solves that. The AI sees all the layouts simultaneously and can pull out a natural language analysis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The math works out too: one call per section instead of one per (section × viewport). An 11× reduction in API calls, with better analysis quality because the model is comparing layouts in context rather than evaluating each in isolation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fi-replaced-playwright-with-raw-cdp%2Fdiagram1-mosaic-compaction.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fi-replaced-playwright-with-raw-cdp%2Fdiagram1-mosaic-compaction.webp" alt="Mosaic compaction pattern: all 11 viewport screenshots composited into one labeled grid image — iphone14-portrait 390x844, android-360x800, ipad-portrait-768, macbook-1280, fhd-1920x1080, 4k-3840x2160, and 5 more — analyzed in a single AI vision call" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The mosaic compaction pattern — 11 viewports → 1 labeled grid → 1 AI vision call. The cell headers are the coordinates. 11× reduction in API calls.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Label→Mapping Loop
&lt;/h2&gt;

&lt;p&gt;The output structure is what makes this a pattern rather than a one-off hack.&lt;/p&gt;

&lt;p&gt;The vision prompt asks for strict JSON keyed by viewport slug:&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;"section_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"section-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"viewport_results"&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;"iphone14-portrait"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"issues"&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;"ultrawide-3440x1440"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty_space"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Content occupies ~30% of horizontal space at 3440px — missing max-width constraint."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"suggested_css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@media (min-width: 2400px) { .hero { max-width: 1800px; margin: 0 auto; } }"&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;The labels in the mosaic header become the keys in the output JSON. No post-processing, no coordinate math, no trying to figure out what the AI "meant" — the structure maps directly to the input labels.&lt;/p&gt;

&lt;p&gt;That's the loop: you label your inputs, the AI returns findings indexed by those labels. Structured output from unstructured visual analysis.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fi-replaced-playwright-with-raw-cdp%2Fdiagram2-label-mapping.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fi-replaced-playwright-with-raw-cdp%2Fdiagram2-label-mapping.webp" alt="Label-to-mapping loop diagram: viewport slug labels embedded in mosaic cells map directly to JSON response keys — iphone14-portrait, android-360x800, fhd-1920x1080 — with status, findings, and severity fields per viewport" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The label→mapping loop: viewport slugs in the mosaic become keys in the JSON — no post-processing, no coordinate math, direct structured output from visual analysis&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What It Actually Caught
&lt;/h2&gt;

&lt;p&gt;I ran this pipeline on the SurgiQuip proposal page — a password-gated, multi-section client site I'd been building.&lt;/p&gt;

&lt;p&gt;The result: it caught everything. Every single thing.&lt;/p&gt;

&lt;p&gt;Every layout break I'd missed during development, every section that needed &lt;code&gt;max-width&lt;/code&gt; handling at wide viewports, every place where the responsive grid didn't collapse cleanly. After re-running the resizes based on the AI's CSS suggestions, every aspect ratio worked.&lt;/p&gt;

&lt;p&gt;The AI suggestions aren't a push-button fix — they're a starting point that still needs a human review before applying. "Looks right in the mosaic" isn't the same as "verified in a real browser." But as a first-pass audit that catches structural problems before a client sees them, it's genuinely remarkable.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of &lt;a href="https://htek.dev/articles/vibe-testing-when-ai-agents-goodhart-your-test-suite/" rel="noopener noreferrer"&gt;AI-augmented QA pattern&lt;/a&gt; that doesn't replace human judgment — it surfaces what human eyes would miss.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where Else This Pattern Applies
&lt;/h2&gt;

&lt;p&gt;When I stepped back after the build, the obvious question was: is this just for responsive testing? Honestly, no.&lt;/p&gt;

&lt;p&gt;"You can do all kinds of stuff with this pattern. I found that fascinating." That's what I keep coming back to.&lt;/p&gt;

&lt;p&gt;The compaction pattern solves a general problem: how do you get structured AI feedback across multiple visual states without making N separate API calls?&lt;/p&gt;

&lt;p&gt;A few directions this applies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-state UI comparison.&lt;/strong&gt; Composite "empty", "loading", "populated", "error" states of the same component side by side. Ask AI: "Which states have accessibility issues?" One call, structured answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before/after design diffs.&lt;/strong&gt; Instead of perceptual diffs, composite old vs. new side by side and ask AI: "What changed? Is any change unintentional?" Semantic diff instead of pixel diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-browser visual regression.&lt;/strong&gt; Same page, Chrome vs. Firefox vs. Safari, composited. AI spots rendering inconsistencies that diffs would catch, but also tells you &lt;em&gt;what kind&lt;/em&gt; of inconsistency it is.&lt;/p&gt;

&lt;p&gt;The key in all cases: labels in the mosaic become keys in the output JSON. You control the structure by controlling the labels.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Honest Limits
&lt;/h2&gt;

&lt;p&gt;This pipeline requires Chrome running locally with &lt;code&gt;--remote-debugging-port&lt;/code&gt;. It doesn't run in a standard CI environment out of the box — you'd need headless Chrome configured to accept CDP connections, which is possible but not the default GitHub Actions setup.&lt;/p&gt;

&lt;p&gt;Label quality directly affects analysis precision. Vague labels like &lt;code&gt;section-01&lt;/code&gt; give vague feedback. Section IDs and heading text embedded in the mosaic header give the AI something to reason about specifically.&lt;/p&gt;

&lt;p&gt;And the CSS suggestions need human review. The AI is pattern-matching against known layout problems — it will catch &lt;code&gt;max-width&lt;/code&gt; issues reliably, but complex responsive grid fixes should be read carefully before applying. This is an augmentation tool, not an autopilot.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Tool Is in the Repo
&lt;/h2&gt;

&lt;p&gt;The full pipeline lives in &lt;code&gt;tools/responsive-design-testing/&lt;/code&gt; — six scripts that chain together: &lt;code&gt;capture.mjs&lt;/code&gt; (raw CDP), &lt;code&gt;composite.mjs&lt;/code&gt; (sharp grid), &lt;code&gt;analyze.mjs&lt;/code&gt; (vision queue builder), &lt;code&gt;report.mjs&lt;/code&gt;, &lt;code&gt;fix.mjs&lt;/code&gt;, and &lt;code&gt;run.mjs&lt;/code&gt; as the single-command orchestrator.&lt;/p&gt;

&lt;p&gt;Single-command usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools/responsive-design-testing/run.mjs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://yoursite.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;optional-gate-pw&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using AI in your workflow and need visual validation of any kind — not just responsive testing — the compaction pattern is worth adding to your toolkit. The insight isn't the CDP part. It's the label→mapping loop. Once you see it, you'll find uses for it everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The agent chose a better tool than I would have, and in doing so, invented an approach I hadn't considered. Fewer abstraction layers meant more direct control over viewport behavior. One labeled composite per section meant 11× fewer API calls with better cross-viewport analysis.&lt;/p&gt;

&lt;p&gt;That's two good ideas from one build — neither of which was in my original plan.&lt;/p&gt;

&lt;p&gt;The pattern generalizes. Any time you need structured AI feedback across multiple visual states — responsive breakpoints, component states, browser diffs, before/after comparisons — compaction is the pattern. Label your inputs, get output mapped to those labels, skip the coordinate math entirely.&lt;/p&gt;

&lt;p&gt;What would you use it for?&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chromedevtools.github.io/devtools-protocol/" rel="noopener noreferrer"&gt;Chrome DevTools Protocol Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride" rel="noopener noreferrer"&gt;Emulation.setDeviceMetricsOverride&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot" rel="noopener noreferrer"&gt;Page.captureScreenshot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sharp.pixelplumbing.com/" rel="noopener noreferrer"&gt;sharp — High-performance Node.js image processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/two-client-sites-three-days-agentive-context-engineering/" rel="noopener noreferrer"&gt;Two Client Sites in 3 Days&lt;/a&gt; — the client project where this ran&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/vibe-testing-when-ai-agents-goodhart-your-test-suite/" rel="noopener noreferrer"&gt;Vibe Testing: When AI Agents Goodhart Your Test Suite&lt;/a&gt; — the AI testing trust problem&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;What Is Context Engineering?&lt;/a&gt; — the broader discipline this fits into&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>devex</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>I'm Hunting for My Vertical</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:59:03 +0000</pubDate>
      <link>https://dev.to/htekdev/im-hunting-for-my-vertical-76e</link>
      <guid>https://dev.to/htekdev/im-hunting-for-my-vertical-76e</guid>
      <description>&lt;p&gt;One week. Five industries. One discovery that changed how I think about the next decade of software.&lt;/p&gt;

&lt;p&gt;I built an agentic financial advisor, a legal advisor, a marketing tool, a scheduling assistant, and a medical workflow tool. Each was AI-powered. Each was genuinely useful. Each took me a few days to build.&lt;/p&gt;

&lt;p&gt;The part that unsettled me wasn't how fast I could build. It was what the pattern meant.&lt;/p&gt;

&lt;p&gt;Every vertical had a completely different character — its own compliance structure, its own procedural data, its own relationship dynamics. The more I understood a specific vertical's inner workings, the more powerful the AI outputs became. Conversely, the moment I built something generic — something for "everyone" — the value diluted immediately.&lt;/p&gt;

&lt;p&gt;The moat isn't in the model. It isn't in the framework. It isn't even in how fast you can ship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The moat is the vertical.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Floor Has Dropped Out
&lt;/h2&gt;

&lt;p&gt;Before we talk about why verticals win, we need to be clear about what they're winning against.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=gwW8GKwHB3I" rel="noopener noreferrer"&gt;Jensen Huang said it plainly on the All-In Podcast this year&lt;/a&gt;: the competitive advantage in the AI era is no longer which model you run or how fast you can build. It's the vertical knowledge you bring to it. The moat is knowing more about a specific domain than anyone else and using AI to compound that knowledge gap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/hObRMv6qCi0" rel="noopener noreferrer"&gt;Nikesh Arora, CEO of Palo Alto Networks, went further on the All-In Podcast this week&lt;/a&gt;: analytical SaaS is structurally dead. His argument: analytics companies exist to compress and synthesize context. That is exactly what any capable model does now, in seconds. The entire business model of "take in data, analyze it, give you something synthesized" has been replicated for free by any developer with a decent API key.&lt;/p&gt;

&lt;p&gt;I've built those tools myself. Not as products — almost subconsciously, as a side effect of exploring an idea. The floor for horizontal software capability has dropped out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://a16z.com/podcast/why-ai-moats-still-matter-and-how-theyve-changed/" rel="noopener noreferrer"&gt;a16z called it a moat migration last December&lt;/a&gt;: the moats haven't disappeared, but they've moved. Off the platform layer. Into the domain layer. &lt;a href="https://activantcapital.com/research/vertical-software-is-having-a-moment" rel="noopener noreferrer"&gt;Activant Capital framed it simply in their February 2025 analysis&lt;/a&gt;: the industry context that used to be a feature is now the product itself.&lt;/p&gt;

&lt;p&gt;Horizontal capability is not the moat anymore. It is the price of entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Verticals Win
&lt;/h2&gt;

&lt;p&gt;During my week of building across five industries, one engagement hit differently. I was working on a medical device servicing application — the kind of tool that tracks maintenance procedures, compliance documentation, and field technician workflows for hospital equipment.&lt;/p&gt;

&lt;p&gt;What I found was a textbook example of why vertical depth creates defensible moats that horizontal tools can't touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance power.&lt;/strong&gt; Hospitals make massive investments in medical devices. Once you're qualified to service that equipment — once you're in their system, accredited, embedded in their workflow — switching is genuinely hard. Add AI that learns their specific device fleet, their procedure history, their technician notes? The moat deepens with every service call. The longer you're in, the more structurally irreplaceable you become.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proprietary data.&lt;/strong&gt; Medical devices run on ancient technology, but they're extraordinarily verbose. Specs, error codes, procedure manuals, maintenance logs — it's all procedural, structured, richly contextual. No generic inventory app has this data. A purpose-built vertical application that accumulates it over years is in a different category entirely. &lt;a href="https://insights.euclid.vc/p/does-ai-threaten-vertical-saas" rel="noopener noreferrer"&gt;Euclid Ventures describes this as the layer commoditization cycle inverting&lt;/a&gt;: vertical players who own deep domain data become &lt;em&gt;more&lt;/em&gt; valuable as the horizontal layer commoditizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relationships and infrastructure.&lt;/strong&gt; The relationship a medical device service company has with a hospital isn't just commercial — it's operational. Field techs know the equipment. Schedulers know the facilities manager. AI layered into those workflows doesn't just make things faster; it makes the relationship stickier. You're not selling software anymore. You're part of the hospital's operational continuity.&lt;/p&gt;

&lt;p&gt;This pattern exists in every high-relationship, high-compliance vertical: construction, energy, legal, logistics. The specifics change. The structure doesn't. Generic tools exist for all of them. &lt;a href="https://www.servicebridge.com" rel="noopener noreferrer"&gt;ServiceBridge&lt;/a&gt; handles field service dispatch for general contractors. Generic inventory apps cover dozens of verticals. But "general" is not "deep." A tool built for the medical device servicing vertical — one that knows the specific procedural documentation, compliance requirements, and switching costs of that niche — isn't ServiceBridge. It's something that only gets built by someone who went truly, irreversibly deep.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8615zfr1dddrxyzuc3l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8615zfr1dddrxyzuc3l.webp" alt="The Three Moats of the Vertical AI Company — Compliance Power, Proprietary Data, and Relationships &amp;amp; Infrastructure as three pillars built on deep vertical context" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The three moats generic AI tools can't replicate: compliance power, proprietary data, and operational relationships — all compounded through accumulated vertical depth.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Context Payoff
&lt;/h2&gt;

&lt;p&gt;Here's what nobody talks about: when you go deep enough into a vertical, something remarkable happens. Your accumulated domain knowledge becomes a structural weapon that no generalist can replicate.&lt;/p&gt;

&lt;p&gt;The ability to build software is no longer a strong asset.&lt;br&gt;&lt;br&gt;
Being able to execute a workflow is no longer a strong asset.&lt;br&gt;&lt;br&gt;
Knowing &lt;em&gt;what&lt;/em&gt; workflow to execute is the strong asset.&lt;br&gt;&lt;br&gt;
Knowing &lt;em&gt;what context to bring in&lt;/em&gt; is the asset.&lt;/p&gt;

&lt;p&gt;The context is the asset. But here's the key: &lt;strong&gt;the context doesn't exist in isolation. It flows from the vertical.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomyae2zim8g8iyn2gods.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomyae2zim8g8iyn2gods.webp" alt="The Context Hierarchy — four levels from commoditized capability to the context asset, showing how 'knowing what context to bring in' is the ultimate moat" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Capability is table stakes. The competitive weapon is knowing which context to inject — and that knowledge only comes from going deep in a vertical.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A generalist with access to the best available model still doesn't know the compliance calendar of a mid-sized orthopedic device distributor in the Southeast. That knowledge — accumulated through years of patient relationship-building, procedural specificity, and domain learning — can't be generalized away. It can't be scraped. It can't be approximated from public data.&lt;/p&gt;

&lt;p&gt;This is why I've been thinking about this in terms of vertical specialization, not just "context engineering." When I wrote about &lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents" rel="noopener noreferrer"&gt;what context engineering actually looks like at scale&lt;/a&gt;, the model kept being a commodity — the real discipline was shaping what the model sees. The same principle applies at the company level. The company that owns the vertical owns the best context. &lt;a href="https://www.stax.com/insights/how-ai-is-reshaping-vertical-saas" rel="noopener noreferrer"&gt;Stax's 2026 analysis of vertical SaaS reached the same conclusion&lt;/a&gt;: rather than flattening vertical software, AI is separating the companies with deep domain data from those without it.&lt;/p&gt;

&lt;p&gt;Deep wins. Generic loses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agentic Development Company
&lt;/h2&gt;

&lt;p&gt;Here's the structural opportunity I think we're dramatically underbuilding toward.&lt;/p&gt;

&lt;p&gt;Between the hyperscalers — the foundation model providers building AI infrastructure — and the SMBs and mid-markets that need AI-native workflows, there's a missing layer. Someone has to own the vertical workflow integration. Someone has to take general-purpose AI capability and make it fluent in the operational language of a specific industry.&lt;/p&gt;

&lt;p&gt;That's the agentic development company.&lt;/p&gt;

&lt;p&gt;Not a software consultancy. Not an IT services firm. An agentic-first vertical specialist that builds, owns, and continuously deepens AI-native workflows for one target industry. &lt;a href="https://a16z.com/oil-wells-vs-pipelines-two-strategies-for-building-ai-companies/" rel="noopener noreferrer"&gt;a16z framed the strategic choice as oil wells vs. pipelines&lt;/a&gt;: oil wells drill deep into proprietary data and domain relationships; pipelines move generic data efficiently. The agentic development company is an oil well operation. You go deep on one vertical. You build systems that understand it at a level no horizontal tool can match.&lt;/p&gt;

&lt;p&gt;This is the new Accenture moment — but for the long tail of SMBs the original system integrators never served. Every vertical that runs on high-relationship, high-compliance, high-procedural context is an open field right now.&lt;/p&gt;

&lt;p&gt;I wrote about this pattern in terms of &lt;a href="https://htek.dev/articles/agentic-development-maturity-curve" rel="noopener noreferrer"&gt;the agentic development maturity curve&lt;/a&gt;: mastery looks like simplicity because experts stop building everything and start targeting what moves the needle. Going vertical is the same principle applied to market strategy. &lt;a href="https://htek.dev/articles/frameworks-dont-execute-themselves" rel="noopener noreferrer"&gt;Frameworks don't execute themselves&lt;/a&gt; — and general-purpose software doesn't execute your specific compliance workflow either. The execution layer belongs to whoever owns the vertical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hunt
&lt;/h2&gt;

&lt;p&gt;I've spent most of my career as a general engineer. I can build anything — full-stack, DevOps, agentic systems, enterprise platforms. The breadth was the point. For a long time, it was valuable.&lt;/p&gt;

&lt;p&gt;It still is. But the game has changed.&lt;/p&gt;

&lt;p&gt;The ability to build is now table stakes. Every ambitious engineer I know can spin up an AI-powered tool in a week. The question is no longer &lt;em&gt;can you build it?&lt;/em&gt; It's &lt;em&gt;which vertical do you own?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm on the hunt for mine. I want to take everything I've built — the agentic development systems, the DevOps depth, the enterprise platform experience — and target a specific vertical deeply enough that the context I accumulate becomes structurally irreplaceable. Not just a tool. An institution.&lt;/p&gt;

&lt;p&gt;Jensen's framing landed because it confirmed something I'd already felt empirically after that week of building. The model isn't the advantage. The industry is.&lt;/p&gt;

&lt;p&gt;If you're at the same inflection point — a general engineer who can build anything, wondering whether breadth is still the edge — I'd argue: pick your vertical. Go deep. The context richness will follow.&lt;/p&gt;

&lt;p&gt;The moat isn't your model. It isn't your framework. It's the vertical you own.&lt;/p&gt;




&lt;p&gt;I'm on the hunt for a vertical worth owning — one where ambitious people want to fundamentally change how their industry runs with AI. If that's you — if you're an operator or leader inside a specific vertical, serious about what agentic capability could do there — &lt;a href="https://htek.dev/consulting" rel="noopener noreferrer"&gt;I want to hear from you&lt;/a&gt;. Not looking for a client. Looking for the right vertical.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=gwW8GKwHB3I" rel="noopener noreferrer"&gt;Jensen Huang: Nvidia's Future, Physical AI, Rise of the Agent&lt;/a&gt; — All-In Podcast (YouTube)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/hObRMv6qCi0" rel="noopener noreferrer"&gt;Palo Alto Networks CEO: "AI Found 5 Years of Bugs in 6 Weeks"&lt;/a&gt; — All-In Podcast featuring Nikesh Arora (YouTube, June 8, 2026)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://a16z.com/podcast/why-ai-moats-still-matter-and-how-theyve-changed/" rel="noopener noreferrer"&gt;Why AI Moats Still Matter (And How They've Changed)&lt;/a&gt; — a16z, December 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://a16z.com/oil-wells-vs-pipelines-two-strategies-for-building-ai-companies/" rel="noopener noreferrer"&gt;Oil Wells vs. Pipelines: Two Strategies for Building AI Companies&lt;/a&gt; — a16z, August 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://insights.euclid.vc/p/does-ai-threaten-vertical-saas" rel="noopener noreferrer"&gt;Does AI Threaten Vertical SaaS?&lt;/a&gt; — Euclid Ventures, June 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://activantcapital.com/research/vertical-software-is-having-a-moment" rel="noopener noreferrer"&gt;Vertical Software Is Having A Moment&lt;/a&gt; — Activant Capital, February 2025&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.stax.com/insights/how-ai-is-reshaping-vertical-saas" rel="noopener noreferrer"&gt;How AI is Reshaping Vertical SaaS&lt;/a&gt; — Stax, February 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.servicebridge.com" rel="noopener noreferrer"&gt;ServiceBridge&lt;/a&gt; — field service management for general contractors&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agenticdevelopment</category>
      <category>aiagents</category>
      <category>contextengineering</category>
      <category>opinion</category>
    </item>
    <item>
      <title>Your GitHub Actions Don't Need Secrets</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Fri, 05 Jun 2026 18:54:27 +0000</pubDate>
      <link>https://dev.to/htekdev/github-actions-at-enterprise-scale-the-identity-first-platform-that-took-us-from-3-teams-to-1000-4b88</link>
      <guid>https://dev.to/htekdev/github-actions-at-enterprise-scale-the-identity-first-platform-that-took-us-from-3-teams-to-1000-4b88</guid>
      <description>&lt;h2&gt;
  
  
  Copy-Paste Workflows Don't Scale
&lt;/h2&gt;

&lt;p&gt;Every platform team hits the same wall. You start with a handful of repos, each with bespoke CI/CD workflows. Twelve months later you have 200 repos, and every deployment pipeline is a snowflake. Engineers copy YAML from Slack threads. Secrets sprawl across repositories. Nobody can answer "who deployed what, and with which permissions?"&lt;/p&gt;

&lt;p&gt;I hit this wall at a Fortune 500 energy company, managing CI/CD for an enterprise DevOps platform. We went from 2–3 teams to &lt;strong&gt;300 teams across roughly 1,000 repositories&lt;/strong&gt; — all on GitHub Actions — in under two years. The secret wasn't better YAML. It was treating Actions as a &lt;strong&gt;platform engineering problem&lt;/strong&gt;, starting from identity.&lt;/p&gt;

&lt;p&gt;GitHub Actions processed &lt;a href="https://github.blog/news-insights/product-news/lets-talk-about-github-actions/" rel="noopener noreferrer"&gt;11.5 billion minutes in 2025 alone&lt;/a&gt; — up 35% year-over-year — with 71 million jobs running per day on its re-architected backend. At that scale, the question isn't "does Actions work?" — it's "how do you govern it without becoming a bottleneck?"&lt;/p&gt;

&lt;p&gt;Here's the recipe: &lt;strong&gt;identify bottlenecks → codify them → scale identity.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Subject Claim Problem (And Why I Built an OIDC Broker)
&lt;/h2&gt;

&lt;p&gt;GitHub Actions supports &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect" rel="noopener noreferrer"&gt;OpenID Connect (OIDC) federation&lt;/a&gt; for passwordless cloud authentication. In theory, every workflow gets a short-lived token scoped to its repo. No more long-lived secrets sitting in repository settings.&lt;/p&gt;

&lt;p&gt;In practice? The &lt;code&gt;sub&lt;/code&gt; (subject) claim in GitHub's OIDC token has a structural limitation: when you call a reusable workflow, the token's subject reflects the &lt;em&gt;caller&lt;/em&gt; context, not the &lt;em&gt;called&lt;/em&gt; workflow. This makes it difficult to enforce "only this approved deployment workflow can authenticate to production Azure resources" — because the subject claim doesn't consistently identify which reusable workflow is executing.&lt;/p&gt;

&lt;p&gt;GitHub has since added &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#using-openid-connect-with-reusable-workflows" rel="noopener noreferrer"&gt;&lt;code&gt;job_workflow_ref&lt;/code&gt;&lt;/a&gt; as a custom claim and introduced &lt;a href="https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens" rel="noopener noreferrer"&gt;immutable subject claims&lt;/a&gt; (enforced for new repos, renames, and transfers after June 18, 2026 — existing repos can opt in now). But when I was building this platform, those features didn't exist yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My solution: a custom OIDC server acting as an identity broker.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The broker accepts a GitHub Actions OIDC token, validates it against the caller's identity, checks the requested scope against a centralized policy, and issues a &lt;em&gt;new&lt;/em&gt; scoped token for Azure. Think of it as an identity translation layer sitting between GitHub and your cloud provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyour-github-actions-dont-need-secrets%2Foidc-broker-architecture.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyour-github-actions-dont-need-secrets%2Foidc-broker-architecture.webp" alt="OIDC broker architecture showing GitHub Actions token exchange through a centralized policy-checked identity translation layer to Azure" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The custom OIDC broker validates GitHub tokens, checks centralized policy, and issues least-privilege Azure credentials — eliminating long-lived secrets entirely.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At the heart of the broker is a standard OAuth2 client credentials flow — one &lt;code&gt;/token&lt;/code&gt; endpoint, three operations:&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;// OIDC broker — token exchange endpoint (routes/github.ts, condensed)&lt;/span&gt;
&lt;span class="nx"&gt;router&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/github/.well-known/token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client_assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job-workflow-ref&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Verify the GitHub Actions OIDC token against GitHub's public JWKS&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;jwk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client_assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;githubJwksClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;issuer&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://token.actions.githubusercontent.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;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Gate access — only your enterprise can use this broker&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;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enterprise&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;your-enterprise-slug&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Derive a controlled sub claim from job_workflow_ref.&lt;/span&gt;
  &lt;span class="c1"&gt;//    This is the fix for the sub-claim problem: the BROKER controls the subject,&lt;/span&gt;
  &lt;span class="c1"&gt;//    not GitHub, so Azure federated credential policies are reliable for&lt;/span&gt;
  &lt;span class="c1"&gt;//    reusable workflows regardless of who called them.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;job_workflow_ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refs/heads/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// → "org/repo/.github/workflows/deploy.yml@main"&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Re-issue a JWT signed with the broker's RSA private key.&lt;/span&gt;
  &lt;span class="c1"&gt;//    Azure trusts this because the broker's /jwks endpoint is registered&lt;/span&gt;
  &lt;span class="c1"&gt;//    as a federated identity credential on the Entra ID application.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api://AzureADTokenExchange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/github`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;jti&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RS256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;keyid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;brokerKeyThumbprint&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sub&lt;/code&gt; derivation on step 3 is the entire point. GitHub's raw OIDC token produces an unpredictable subject when reusable workflows are involved — the broker re-signs with &lt;code&gt;job_workflow_ref&lt;/code&gt; as a stable, auditable identity. Azure's federated credential policy can now reliably match on "only &lt;em&gt;this&lt;/em&gt; approved workflow can authenticate to production."&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="c1"&gt;# A team's CD workflow — the entire Azure auth chain is one step&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;🚀 CD&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&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;created&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name == 'pull_request' &amp;amp;&amp;amp; 'dev' || 'prod' }}&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;deploy&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;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ENVIRONMENT }}&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;   &lt;span class="c1"&gt;# required for OIDC token request&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&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@v4&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;🔑 Login to Azure&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;&amp;lt;your-org&amp;gt;/platform-framework/actions/azure-login@main&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;iam-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ENVIRONMENT }}&lt;/span&gt;        &lt;span class="c1"&gt;# 'dev' or 'prod' — matches iam.yml job name&lt;/span&gt;
          &lt;span class="na"&gt;iam-connection-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AZURE_CREDENTIALS&lt;/span&gt;   &lt;span class="c1"&gt;# matches iam.yml credential binding&lt;/span&gt;
          &lt;span class="na"&gt;secrets-as-json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ toJson(secrets) }}&lt;/span&gt;  &lt;span class="c1"&gt;# platform reads clientId from here&lt;/span&gt;
          &lt;span class="na"&gt;vars-as-json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ toJson(vars) }}&lt;/span&gt;         &lt;span class="c1"&gt;# platform reads tenantId/subscriptionId from here&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;Deploy to Azure App Service&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;azure/webapps-deploy@v2&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;app-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.APP_NAME }}&lt;/span&gt;
          &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single composite action became the foundation everything else was built on. Every team authenticates the same way. Every permission is centrally governed. No secrets in repos.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Framework Stack: Each Framework = GitHub App + Identity + Reusable Workflow
&lt;/h2&gt;

&lt;p&gt;With centralized identity solved, I layered five frameworks on top — each following the same architecture pattern:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyour-github-actions-dont-need-secrets%2Fframework-stack.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyour-github-actions-dont-need-secrets%2Fframework-stack.webp" alt="Layered platform architecture showing identity foundation supporting 5 framework pillars (IAM, Secrets, IAC, Docs, Config) consumed by 300 teams" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each framework follows the same pattern: GitHub App + Entra ID App + Reusable Workflow — all built on the shared identity layer.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;What Teams Define&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IAM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Identity and access management&lt;/td&gt;
&lt;td&gt;RBAC roles in a YAML workflow file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secrets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Central Key Vault management&lt;/td&gt;
&lt;td&gt;Secret names and scopes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Infrastructure as Code (Bicep → Azure)&lt;/td&gt;
&lt;td&gt;Bicep modules and parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Centralized documentation deployment&lt;/td&gt;
&lt;td&gt;Markdown content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Config&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Configuration management&lt;/td&gt;
&lt;td&gt;Environment variables and app settings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each framework consists of three components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A GitHub App&lt;/strong&gt; — provides the automation identity and webhook triggers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An Entra ID (Azure AD) app&lt;/strong&gt; — holds the federated credential with scoped permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A reusable workflow&lt;/strong&gt; — the actual pipeline logic teams call from their repos&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The IAM Framework: The Crown Jewel
&lt;/h3&gt;

&lt;p&gt;The IAM framework is where this architecture pays off most dramatically. Here's the team experience:&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="c1"&gt;# .github/workflows/iam.yml&lt;/span&gt;
&lt;span class="c1"&gt;# Merge this PR → the IAM framework auto-provisions Entra ID apps,&lt;/span&gt;
&lt;span class="c1"&gt;# federated credentials, and RBAC assignments for every environment.&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;📋 Platform | Identity and Access Management&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/iam.yml'&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;dev&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;&amp;lt;your-org&amp;gt;/platform-iam/.github/workflows/define.yml@main&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
      &lt;span class="na"&gt;definitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;github/env/dev/AZURE_CREDENTIALS&lt;/span&gt;
        &lt;span class="s"&gt;rbac/subscriptions/&amp;lt;dev-subscription-id&amp;gt;/Contributor&lt;/span&gt;
        &lt;span class="s"&gt;rbac/subscriptions/&amp;lt;dev-subscription-id&amp;gt;/Azure Deployment Stack Owner&lt;/span&gt;
        &lt;span class="s"&gt;rbac/subscriptions/&amp;lt;hub-subscription-id&amp;gt;/resourceGroups/rg-dns-hub/Private DNS Zone Contributor&lt;/span&gt;

  &lt;span class="na"&gt;prod&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;&amp;lt;your-org&amp;gt;/platform-iam/.github/workflows/define.yml@main&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
      &lt;span class="na"&gt;definitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;github/env/prod/AZURE_CREDENTIALS&lt;/span&gt;
        &lt;span class="s"&gt;rbac/subscriptions/&amp;lt;prod-subscription-id&amp;gt;/Contributor&lt;/span&gt;
        &lt;span class="s"&gt;rbac/subscriptions/&amp;lt;prod-subscription-id&amp;gt;/Azure Deployment Stack Owner&lt;/span&gt;
        &lt;span class="s"&gt;rbac/subscriptions/&amp;lt;hub-subscription-id&amp;gt;/resourceGroups/rg-dns-hub/Private DNS Zone Contributor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a team pushes this file, the IAM framework:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates an Entra ID application registration&lt;/li&gt;
&lt;li&gt;Configures federated credentials tied to their specific repo&lt;/li&gt;
&lt;li&gt;Stores the client ID as a repository variable&lt;/li&gt;
&lt;li&gt;Sets up RBAC assignments in Azure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The team then calls the login composite action with a version tag — that's it. Zero portal clicks. Zero tickets. Full auditability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: a new team goes from "we need Azure access" to "we're deploying to production" in a single PR review cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scaling Arc: Patterns That Actually Matter
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://pith.science/paper/2601.11299" rel="noopener noreferrer"&gt;2025 practitioner survey of 419 GitHub Actions users&lt;/a&gt; found that while reusable &lt;em&gt;actions&lt;/em&gt; see heavy adoption, reusable &lt;em&gt;workflows&lt;/em&gt; remain underutilized — largely because teams fear versioning complexity and loss of control. This matches what I observed: teams resist reuse unless the abstraction is genuinely simpler than copy-paste.&lt;/p&gt;

&lt;p&gt;The patterns that made reuse stick:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Composite Actions as the Building Block
&lt;/h3&gt;

&lt;p&gt;Composite actions (not reusable workflows) are where you start. They're simpler to version, test, and compose. Our &lt;code&gt;login-to-azure&lt;/code&gt; action is called by every framework's reusable workflow — it's the atomic unit.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Reusable Workflows as Contracts
&lt;/h3&gt;

&lt;p&gt;Reusable workflows define the &lt;em&gt;contract&lt;/em&gt; — "this is how you deploy infrastructure" or "this is how docs get published." GitHub recently expanded these to support &lt;a href="https://github.blog/news-insights/product-news/lets-talk-about-github-actions/" rel="noopener noreferrer"&gt;10 levels of nesting and 50 workflow calls per run&lt;/a&gt;, which validates the deep composition patterns we built early.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Trigger Type Literacy
&lt;/h3&gt;

&lt;p&gt;The most underrated skill in Actions at scale: understanding trigger types deeply. &lt;code&gt;workflow_call&lt;/code&gt; vs &lt;code&gt;workflow_dispatch&lt;/code&gt; vs &lt;code&gt;repository_dispatch&lt;/code&gt; each has fundamentally different trust boundaries and token behaviors. Most engineers treat them interchangeably — and then get bitten by permission escalation or silent failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Central Repos as the Source of Truth
&lt;/h3&gt;

&lt;p&gt;Each framework lives in a dedicated repo. Teams never fork — they call with version tags. Updates propagate instantly. Governance lives in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  From CI/CD to Intelligent System
&lt;/h2&gt;

&lt;p&gt;The final evolution was adding intelligence on top of the platform. Using webhooks and GitHub Issues, we built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered issue categorization&lt;/strong&gt;: incoming platform issues get triaged automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated release notes&lt;/strong&gt;: framework releases generate changelogs from PR descriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy drift detection&lt;/strong&gt;: nightly runs compare actual Azure state against declared YAML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this required a separate tool. The identity layer, the reusable workflows, and the event system were already there. Intelligence was just another consumer of the same platform primitives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Playbook: The Three-Step Recipe
&lt;/h2&gt;

&lt;p&gt;If you're staring at 50+ repos with snowflake workflows, here's the path:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyour-github-actions-dont-need-secrets%2Fthree-step-playbook.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyour-github-actions-dont-need-secrets%2Fthree-step-playbook.webp" alt="Three-step enterprise scaling playbook: 1. Solve Identity First, 2. Build Frameworks Not Pipelines, 3. Scale Identity Not Humans" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The three-step recipe that scales from 3 teams to 1,000 repos: centralize identity, codify frameworks, let identity scale itself.&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solve identity first.&lt;/strong&gt; Whether you use GitHub's native OIDC (with the newer &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect" rel="noopener noreferrer"&gt;&lt;code&gt;job_workflow_ref&lt;/code&gt; claims&lt;/a&gt; and &lt;a href="https://github.blog/changelog/2026-03-12-actions-oidc-tokens-now-support-repository-custom-properties" rel="noopener noreferrer"&gt;repository custom properties&lt;/a&gt;) or build a broker — centralized, auditable identity is your foundation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build frameworks, not pipelines.&lt;/strong&gt; Each framework should be composable (composite action → reusable workflow → team YAML). Teams should define &lt;em&gt;what&lt;/em&gt; they need, not &lt;em&gt;how&lt;/em&gt; to get it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scale the identity, not the humans.&lt;/strong&gt; When a new team onboards, they shouldn't need a meeting. They define their requirements in YAML, the framework provisions everything, and identity flows through automatically.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://theapplied.co/use-cases/astrazeneca-github-copilot-drug-discovery" rel="noopener noreferrer"&gt;AstraZeneca scaled 5,000 developers across 20,000 repositories&lt;/a&gt; on GitHub Enterprise using similar patterns — reusable Actions libraries with security baked in by default. The pattern works whether you're 50 engineers or 5,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;GitHub Actions at enterprise scale isn't a YAML problem — it's a platform engineering problem. The organizations that scale are the ones that treat identity as infrastructure, workflows as contracts, and frameworks as products with versioned APIs.&lt;/p&gt;

&lt;p&gt;I've written extensively about &lt;a href="https://htek.dev/articles/platform-engineering-github-internal-developer-platform/" rel="noopener noreferrer"&gt;platform engineering with GitHub&lt;/a&gt; and how &lt;a href="https://htek.dev/articles/github-actions-debugging-guide/" rel="noopener noreferrer"&gt;GitHub Actions debugging&lt;/a&gt; fits into this picture. If you're building internal developer platforms, the identity-first approach is the one architecture decision that makes everything else possible.&lt;/p&gt;

&lt;p&gt;The recipe hasn't changed since I scaled to 1,000 repos: &lt;strong&gt;identify bottlenecks → codify them → scale identity.&lt;/strong&gt; Everything else is implementation detail.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>platformengineering</category>
      <category>automation</category>
      <category>azure</category>
    </item>
    <item>
      <title>You're Not Doing GitOps (You're Doing CI/CD With Extra Steps)</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Fri, 05 Jun 2026 18:53:18 +0000</pubDate>
      <link>https://dev.to/htekdev/youre-not-doing-gitops-youre-doing-cicd-with-extra-steps-1467</link>
      <guid>https://dev.to/htekdev/youre-not-doing-gitops-youre-doing-cicd-with-extra-steps-1467</guid>
      <description>&lt;h2&gt;
  
  
  The Uncomfortable Truth
&lt;/h2&gt;

&lt;p&gt;Here's a test: when your deployment fails in production, what happens to your &lt;code&gt;main&lt;/code&gt; branch?&lt;/p&gt;

&lt;p&gt;If the answer is "the broken code is already merged" — congratulations, you're doing CI/CD with a Git trigger. That's not GitOps. It's a pipeline that happens to watch a branch.&lt;/p&gt;

&lt;p&gt;I've spent years building &lt;a href="https://htek.dev/articles/platform-engineering-with-github/" rel="noopener noreferrer"&gt;platform engineering systems at enterprise scale&lt;/a&gt; — identity management frameworks, infrastructure-as-code pipelines, AI agent platforms that manage operational code. And I keep seeing the same mistake: teams adopt "GitOps" by adding a deployment step after merge, then wonder why they get drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;True GitOps has one non-negotiable rule: &lt;code&gt;main&lt;/code&gt; always equals production.&lt;/strong&gt; If a deployment fails, &lt;code&gt;main&lt;/code&gt; doesn't change. Period. This isn't just my opinion — it's the logical extension of &lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps principles&lt;/a&gt;: declarative desired state, versioned in Git, automatically reconciled. The enforcement mechanism I'm describing is how you make those principles real rather than aspirational.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anti-Pattern Everyone Runs
&lt;/h2&gt;

&lt;p&gt;The most common "GitOps" setup I see in enterprise teams looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developer opens PR&lt;/li&gt;
&lt;li&gt;CI runs tests&lt;/li&gt;
&lt;li&gt;Reviewer approves&lt;/li&gt;
&lt;li&gt;PR merges to &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deployment triggers from &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;❌ Deployment fails&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main&lt;/code&gt; now contains code that isn't in production&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyoure-not-doing-gitops-cicd-with-extra-steps%2Fcicd-vs-gitops.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyoure-not-doing-gitops-cicd-with-extra-steps%2Fcicd-vs-gitops.webp" alt="CI/CD vs GitOps workflow comparison — merge-then-deploy creates drift, deploy-then-merge enforces truth" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;CI/CD deploys after merge (drift risk) vs GitOps deploys before merge (main = production)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;merge-then-deploy&lt;/strong&gt;. It's standard CI/CD with extra steps. The moment you merge before confirming a successful deployment, you've broken the core GitOps contract: Git as the single source of truth for what's actually running.&lt;/p&gt;

&lt;p&gt;The result? Drift. Stale state in &lt;code&gt;main&lt;/code&gt;. A branch that lies about what's deployed. Every subsequent PR is now based on a broken foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Enforcement Pattern: Deploy Before Merge
&lt;/h2&gt;

&lt;p&gt;The fix isn't philosophical — it's mechanical. &lt;a href="https://github.blog/engineering/engineering-principles/how-github-uses-merge-queue-to-ship-hundreds-of-changes-every-day/" rel="noopener noreferrer"&gt;GitHub's Merge Queue&lt;/a&gt; gives you exactly the right primitive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developer opens PR&lt;/li&gt;
&lt;li&gt;CI runs tests (standard checks)&lt;/li&gt;
&lt;li&gt;Reviewer approves → PR enters the &lt;strong&gt;merge queue&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Merge queue trigger runs a &lt;strong&gt;dry-run deployment&lt;/strong&gt; against the target environment&lt;/li&gt;
&lt;li&gt;If dry-run passes → queue trigger runs the &lt;strong&gt;live deployment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If live deployment succeeds → PR merges to &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If deployment fails → PR is &lt;strong&gt;rejected&lt;/strong&gt;. &lt;code&gt;main&lt;/code&gt; stays clean.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyoure-not-doing-gitops-cicd-with-extra-steps%2Fmergequeue-flow.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhtek.dev%2Fimages%2Farticles%2Fyoure-not-doing-gitops-cicd-with-extra-steps%2Fmergequeue-flow.webp" alt="MergeQueue enforcement flow — PR enters queue, deploys successfully, then merges to main" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The MergeQueue pattern: code proves it can deploy before it's allowed to merge&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the critical difference. The merge is the &lt;em&gt;receipt&lt;/em&gt;, not the &lt;em&gt;trigger&lt;/em&gt;. By the time code lands in &lt;code&gt;main&lt;/code&gt;, it's already proven it can deploy successfully. &lt;code&gt;main&lt;/code&gt; never lies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/engineering/engineering-principles/how-github-uses-merge-queue-to-ship-hundreds-of-changes-every-day/" rel="noopener noreferrer"&gt;GitHub ships hundreds of changes per day&lt;/a&gt; using exactly this pattern — batch PRs into merge groups, test and deploy the group, merge only on success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Parity: The Force Multiplier
&lt;/h2&gt;

&lt;p&gt;The MergeQueue pattern only works if you've solved the second GitOps requirement: &lt;strong&gt;environment parity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every environment — dev, staging, production — should deploy using the exact same scripts. The only difference is configuration parameters. If your prod deployment uses a different process than dev, you've introduced a variable that the merge queue can't validate.&lt;/p&gt;

&lt;p&gt;Here's the mental model: environments aren't stages in a pipeline. They're instances of the same declaration with different inputs. Your Terraform modules, your Helm charts, your infrastructure definitions — same code, different &lt;code&gt;.tfvars&lt;/code&gt; or &lt;code&gt;values.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is where I see the most breakage. Teams invest in merge queues but maintain hand-rolled production deployment scripts that diverge from their staging process. In my experience, the #1 thing that breaks production is environmental differences — not bad code, not missing tests, but a deployment process that works differently in prod than it did in staging. &lt;a href="https://developer.hashicorp.com/well-architected-framework/define-and-automate-processes/process-automation/gitops" rel="noopener noreferrer"&gt;HashiCorp's Well-Architected Framework&lt;/a&gt; emphasizes this same principle: operational artifacts in Git should be the single declaration that drives all environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Start: The High-Stakes Workflow
&lt;/h2&gt;

&lt;p&gt;If you're onboarding a platform engineer into a GitOps-first team, don't start with app deployments. Start with &lt;strong&gt;networking-as-code&lt;/strong&gt; or &lt;strong&gt;firewall-as-code&lt;/strong&gt; — systems where a failed deployment can be company-destroying.&lt;/p&gt;

&lt;p&gt;Why? Because it forces the right engineering instincts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How do I ensure this deployment succeeds before it's live?"&lt;/li&gt;
&lt;li&gt;"What happens when the pipeline fails halfway through?"&lt;/li&gt;
&lt;li&gt;"How do I roll back without manual intervention?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't theoretical — they're survival questions when you're managing production firewalls through code. The rigor you develop there carries into every other GitOps workflow.&lt;/p&gt;

&lt;p&gt;Infrastructure-as-code for identity management is another excellent starting point. I've built systems where &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/overview" rel="noopener noreferrer"&gt;Entra ID applications with RBAC definitions&lt;/a&gt; are entirely managed through code — every role assignment, every app registration, every permission scope. The MergeQueue pattern here means a misconfigured role never reaches production without a successful dry-run proving it resolves correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Agents Make GitOps More Critical, Not Less
&lt;/h2&gt;

&lt;p&gt;Here's where the conversation gets forward-looking. AI agents — &lt;a href="https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent" rel="noopener noreferrer"&gt;GitHub Copilot coding agent&lt;/a&gt;, autonomous infrastructure bots, custom platform agents — are increasingly the primary authors of operational code. The &lt;a href="https://www.getunleash.io/blog/what-is-the-difference-between-gitops-and-ci-cd" rel="noopener noreferrer"&gt;traditional distinction between GitOps and CI/CD&lt;/a&gt; matters more than ever when machines are the ones making commits.&lt;/p&gt;

&lt;p&gt;This doesn't make GitOps obsolete. It makes it &lt;a href="https://www.firefly.ai/blog/2026-predictions-ai-wont-kill-iac-it-will-make-it-non-negotiable" rel="noopener noreferrer"&gt;non-negotiable&lt;/a&gt;. I've written about &lt;a href="https://htek.dev/articles/how-to-build-governed-ai-agent-systems/" rel="noopener noreferrer"&gt;why governed agent systems need exactly this kind of enforcement&lt;/a&gt; — and the GitOps substrate is how you get there.&lt;/p&gt;

&lt;p&gt;Consider: if an AI agent can codify a process — user onboarding, access provisioning, network configuration — and you have a deterministic sync process validating that code, you can safely let agents manage entire operational domains. The &lt;a href="https://particle41.com/insights/gitops-when-ai-agents-make-commits/" rel="noopener noreferrer"&gt;GitOps pattern becomes the guardrail&lt;/a&gt; that makes autonomous agents viable.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://htek.dev/articles/what-is-context-engineering-practical-guide-50-agents/" rel="noopener noreferrer"&gt;50+ AI agents managing operational code daily&lt;/a&gt;. They don't hit APIs directly — they modify code, which flows through the same MergeQueue validation as human-authored changes. Policy violations surface as deployment failures. The agent's code either passes or it doesn't. No special paths, no elevated privileges, no drift.&lt;/p&gt;

&lt;p&gt;The enforcement pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent proposes a change (PR)&lt;/li&gt;
&lt;li&gt;Merge queue validates deployment&lt;/li&gt;
&lt;li&gt;If it passes: merge. If not: reject.&lt;/li&gt;
&lt;li&gt;The agent is subject to the same rules as any engineer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the industry is heading. &lt;a href="https://www.harness.io/blog/agentic-ai-in-devops-the-architects-guide-to-autonomous-infrastructure" rel="noopener noreferrer"&gt;Harness calls it "agentic AI in DevOps"&lt;/a&gt; — autonomous agents that observe, reason, and act on infrastructure. I've explored this convergence in &lt;a href="https://htek.dev/articles/agent-proof-architecture-agentic-devops/" rel="noopener noreferrer"&gt;agent-proof architecture for agentic DevOps&lt;/a&gt;. But without GitOps as the substrate, autonomous agents become autonomous drift generators.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Litmus Test
&lt;/h2&gt;

&lt;p&gt;Before you call your workflow "GitOps," answer these three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If a deployment fails, does &lt;code&gt;main&lt;/code&gt; still change?&lt;/strong&gt; If yes — that's CI/CD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you reconstruct every environment from Git alone?&lt;/strong&gt; If no — you have drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are agents and humans subject to the same merge rules?&lt;/strong&gt; If no — you have a governance gap.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all three pass, you're doing GitOps. If not, you're doing CI/CD with a Git trigger — and that's fine, but call it what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;GitOps isn't a tooling choice — it's an enforcement philosophy. The core contract is brutally simple: &lt;strong&gt;&lt;code&gt;main&lt;/code&gt; equals production, always&lt;/strong&gt;. The MergeQueue pattern is how you mechanically enforce that contract. Environment parity is how you make it trustworthy. And as AI agents become your primary infrastructure operators, that enforcement isn't just nice-to-have — it's the only thing standing between autonomous agents and uncontrolled drift.&lt;/p&gt;

&lt;p&gt;Stop deploying after merge. Start merging after deployment. That's GitOps.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.blog/engineering/engineering-principles/how-github-uses-merge-queue-to-ship-hundreds-of-changes-every-day/" rel="noopener noreferrer"&gt;How GitHub Uses Merge Queue to Ship Hundreds of Changes Every Day&lt;/a&gt; — GitHub Engineering&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;OpenGitOps Principles&lt;/a&gt; — CNCF&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://particle41.com/insights/gitops-when-ai-agents-make-commits/" rel="noopener noreferrer"&gt;GitOps When AI Agents Are Making Commits&lt;/a&gt; — Particle41&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.firefly.ai/blog/2026-predictions-ai-wont-kill-iac-it-will-make-it-non-negotiable" rel="noopener noreferrer"&gt;AI Won't Kill IaC — It Will Make It Non-Negotiable&lt;/a&gt; — Firefly&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/well-architected-framework/define-and-automate-processes/process-automation/gitops" rel="noopener noreferrer"&gt;HashiCorp Well-Architected Framework: GitOps&lt;/a&gt; — HashiCorp&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.harness.io/blog/agentic-ai-in-devops-the-architects-guide-to-autonomous-infrastructure" rel="noopener noreferrer"&gt;Agentic AI in DevOps: The Architect's Guide&lt;/a&gt; — Harness&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.getunleash.io/blog/what-is-the-difference-between-gitops-and-ci-cd" rel="noopener noreferrer"&gt;What Is the Difference Between GitOps and CI/CD?&lt;/a&gt; — Unleash&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent" rel="noopener noreferrer"&gt;GitHub Copilot Coding Agent&lt;/a&gt; — GitHub Docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/overview" rel="noopener noreferrer"&gt;Microsoft Entra ID Platform Overview&lt;/a&gt; — Microsoft Learn&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>platformengineering</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
