<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://asklar.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://asklar.dev/" rel="alternate" type="text/html" /><updated>2026-09-22T20:43:35-07:00</updated><id>https://asklar.dev/feed.xml</id><title type="html">Alexander Sklar’s AI Insights</title><subtitle>Musings on AI, language models, MCP, and technology from a Principal Software Engineer at Microsoft. Deep technical insights from the trenches of enabling agentic workflows on Windows at enterprise scale.</subtitle><author><name>Alexander Sklar</name></author><entry><title type="html">Your agent can call tools. Can it find them?</title><link href="https://asklar.dev/ai/tools/2026/07/31/ard-explorer.html" rel="alternate" type="text/html" title="Your agent can call tools. Can it find them?" /><published>2026-07-31T00:00:00-07:00</published><updated>2026-07-31T00:00:00-07:00</updated><id>https://asklar.dev/ai/tools/2026/07/31/ard-explorer</id><content type="html" xml:base="https://asklar.dev/ai/tools/2026/07/31/ard-explorer.html"><![CDATA[<p>We’ve got invocation figured out. MCP for tool calls, A2A for agent-to-agent, OpenAPI for services. But ask your agent “what’s available for this task?” and you get nothing back. You’re still manually wiring in every tool, skill, and plugin, hoping you didn’t miss the better option buried in a README three repos deep.</p>

<p>The entire discovery layer is missing from the agentic stack.</p>

<p><img src="/assets/images/posts/ard-explorer/ard-stack.svg" alt="The agentic stack: where discovery fits" /></p>

<h2 id="ard-the-spec">ARD: the spec</h2>

<p><a href="https://agenticresourcediscovery.org/">Agentic Resource Discovery</a> (ARD) is an open spec — v0.9 draft, published under the Linux Foundation. Microsoft, Google, GitHub, Hugging Face, GoDaddy, NVIDIA, Salesforce, Snowflake, Cisco, Databricks, and ServiceNow are all behind it.</p>

<p>It gives registries a standard way to advertise what they have, and gives clients a standard way to search across them. The analogy people reach for is DNS — your agent asks “who can do X?” and the registry answers with matching resources and connection details. It sits before invocation, works with MCP servers, A2A agents, OpenAPI services, CLIs, skills, whatever, and is federated so multiple registries can coexist and cross-reference each other.</p>

<p>The <a href="https://github.com/ards-project/ard-spec">spec</a> defines a query model (text search, faceted filters, pagination), a manifest format for describing resources, conformance rules, and federation referrals that let one registry point you to another.</p>

<p>As <a href="https://aaif.io/blog/the-missing-layer-in-ai-interoperability-discovery/">Angie Jones explains in her writeup on AI Catalog</a>, discovery is an <em>ecosystem</em> problem, not a protocol-specific one. An agent workflow might need an MCP server for market data, an A2A agent for reasoning, a CLI tool for local operations, and a skills plugin for domain prompts. Each artifact type has its own native metadata format — ARD doesn’t try to replace those. It provides a common discovery layer around them: a typed JSON catalog where each entry declares what kind of artifact it is via <code class="language-plaintext highlighter-rouge">mediaType</code>, and clients can fetch and process entries they understand while skipping the rest.</p>

<h2 id="real-implementations-not-just-a-spec">Real implementations, not just a spec</h2>

<p>The reason I’m writing about this now and not in six months is that real implementations already exist:</p>

<p><strong><a href="https://github.com/agentfinder">GitHub Agent Finder</a></strong> — built into GitHub Copilot. Instead of pre-loading every MCP server into the context window, Copilot searches GitHub’s <a href="https://github.com/github/agentfinder-catalog">curated catalog of AI resources</a> (or your own private registry) and surfaces ranked matches on demand. Agent finder only finds — it doesn’t auto-install anything. You stay in control.</p>

<p><strong><a href="https://github.com/huggingface/hf-discover">Hugging Face Discover</a></strong> — provides search access to thousands of skills, ML applications, and MCP servers on Hugging Face and other ARD registries.</p>

<p><strong>Cisco AI Catalog</strong> and <strong>Ora Directory</strong> — additional reference registries you can query today.</p>

<p>Google’s <a href="https://developers.googleblog.com/announcing-the-agentic-resource-discovery-specification/">announcement post</a> and Microsoft’s <a href="https://commandline.microsoft.com/agentic-resource-discovery-specification-ard/">post on the Command Line blog</a> both go into more detail on the motivation and architecture.</p>

<h2 id="the-problem-specs-are-hard-to-explore">The problem: specs are hard to explore</h2>

<p>I’ve been working with ARD, and the usual problem with new specs applies — you want to poke at real endpoints, try queries, see what the response looks like, validate your manifest, but there’s no easy way to do that without writing throwaway code.</p>

<p>MCP had <a href="https://github.com/modelcontextprotocol/inspector">MCP Inspector</a> for this. ARD didn’t have anything equivalent.</p>

<p>So I built one.</p>

<h2 id="ard-explorer">ARD Explorer</h2>

<p><a href="https://github.com/asklar/ard-explorer">ARD Explorer</a> is a web UI for exploring ARD endpoints. One command, zero config:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx ard-explorer
</code></pre></div></div>

<p><img src="/assets/images/posts/ard-explorer/ard-explorer-search.png" alt="ARD Explorer searching GitHub Agent Finder for &quot;pdf&quot; — showing results from OpenAI, Anthropic, and more" /></p>

<p>It opens a local web app where you can:</p>

<ul>
  <li><strong>Connect to any ARD registry</strong> by URL, or discover registries via <code class="language-plaintext highlighter-rouge">.well-known/ai-catalog.json</code></li>
  <li><strong>Search</strong> with the full §7.1 query model — text queries, filters, pagination, federation referrals</li>
  <li><strong>Browse facets</strong> with bucket counts (see what categories and capabilities a registry exposes)</li>
  <li><strong>List agents</strong> via <code class="language-plaintext highlighter-rouge">GET /agents</code></li>
  <li><strong>Validate manifests</strong> against the spec’s conformance rules</li>
  <li><strong>Inspect raw request/response</strong> for every call (useful when you’re implementing a registry yourself)</li>
</ul>

<p>It ships with a built-in mock registry so you can explore the query model even without a live endpoint. And it comes pre-configured with the reference implementations: Hugging Face Discover, GitHub Agent Finder, Cisco AI Catalog, and Ora Directory. Includes a proxy server to handle CORS, since browsers and cross-origin API calls don’t mix.</p>

<h2 id="try-it">Try it</h2>

<p>The agentic ecosystem has a cold start problem. Protocols for calling tools are maturing fast, but discovering tools is still ad-hoc. This isn’t just an MCP problem — it’s true for A2A agents, Skills, Plugins, Canvases, workflows, and every other kind of agentic resource. ARD standardizes that layer, and any artifact type with an agreed-upon media type can participate.</p>

<p>Building a registry? You can test your implementation against the query model. Evaluating ARD as a consumer? See what’s actually out there across GitHub, Hugging Face, Cisco, and more. Just curious? The mock registry lets you play with it without any setup.</p>

<p>The package is on npm as <code class="language-plaintext highlighter-rouge">ard-explorer</code>. The <a href="https://github.com/asklar/ard-explorer">repo is on GitHub</a>.</p>

<hr />

<p><em>I write about building with AI agents. <a href="https://www.linkedin.com/in/asklar/">Follow me on LinkedIn</a> for more.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="AI" /><category term="Tools" /><category term="ard" /><category term="agentic-resource-discovery" /><category term="mcp" /><category term="developer-tools" /><summary type="html"><![CDATA[ARD Explorer is a web UI for the Agentic Resource Discovery spec — the missing discovery layer in the agentic stack.]]></summary></entry><entry><title type="html">AppTasks: Agent Progress on the Windows Taskbar</title><link href="https://asklar.dev/windows/ai/agentic-systems/2026/07/24/apptasks-api.html" rel="alternate" type="text/html" title="AppTasks: Agent Progress on the Windows Taskbar" /><published>2026-07-24T00:00:00-07:00</published><updated>2026-07-24T00:00:00-07:00</updated><id>https://asklar.dev/windows/ai/agentic-systems/2026/07/24/apptasks-api</id><content type="html" xml:base="https://asklar.dev/windows/ai/agentic-systems/2026/07/24/apptasks-api.html"><![CDATA[<p>As agents start running on Windows, users need a way to see what they’re doing at a glance. At <a href="https://techcommunity.microsoft.com/blog/Windows-ITPro-blog/evolving-windows-new-copilot-and-ai-experiences-at-ignite-2025/4469466">Ignite 2025</a>, we showed how Windows is evolving to include agent-like functions built into the OS — including agent tasks on the taskbar. The <a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.shell.tasks?view=winrt-28000"><code class="language-plaintext highlighter-rouge">Windows.UI.Shell.Tasks</code></a> WinRT namespace is the API that makes it work: letting apps put structured task progress directly on the taskbar — no alt-tabbing required.</p>

<p>We designed this in close collaboration with our Shell team. If you’re building agents that run on Windows, this is the API you need to know.</p>

<h2 id="the-problem-it-solves">The problem it solves</h2>

<p>Apps can show detailed info about what’s happening — but that means the user has to be staring at the app. File copies get a whole dialog with a progress chart — hey, I built this with my team back in Windows 8! — with detailed progress, speed estimates, and time remaining (OK, that last one wasn’t always accurate); great, if you’re watching it. It was apparently good enough that <a href="https://github.com/Sanakan8472/copy-dialog-lunar-lander">someone built a lunar lander game on top of it</a>.</p>

<p><img src="/assets/images/apptasks/copy-dialog-lunar-lander.gif" alt="Lunar lander game running inside the Windows file copy dialog" /></p>

<p>But everyone knows people don’t want to sit there staring at a dialog, which is why file copies <em>also</em> show a sweeping progress bar on the taskbar. Downloads do the same.</p>

<p>That taskbar progress bar is the universal “something is happening” signal. But that’s all it tells you. An agent researching a topic might spend minutes crawling sources, cross-referencing, drafting. A progress bar can’t tell you it just finished searching three databases and is now compiling a report. The user has no idea if it’s working, stuck, or waiting for input. They alt-tab. They check. They alt-tab back. Repeat.</p>

<p><img src="/assets/images/apptasks/terminal-progress.gif" alt="Taskbar progress bar" width="80" />
<em>The taskbar progress bar: useful for downloads, not enough for agents.</em></p>

<p>The AppTasks API gives apps a way to push <em>structured</em> task status directly into the Windows Shell — steps, results, even interactive prompts. The taskbar becomes the progress surface. No custom UI required.</p>

<p><img src="/assets/images/apptasks/apptasks-ui-example.png" alt="Researcher agent showing step-by-step progress on the Windows taskbar" />
<em>The Researcher agent in Microsoft 365 Copilot showing real-time progress steps on the taskbar — completed steps get checkmarks, the current step shows a spinner.</em></p>

<h2 id="the-api-surface">The API surface</h2>

<p>The namespace has three classes and one enum. That’s it.</p>

<p><strong><a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.shell.tasks.apptaskinfo?view=winrt-28000"><code class="language-plaintext highlighter-rouge">AppTaskInfo</code></a></strong> is the task itself. Create one with <code class="language-plaintext highlighter-rouge">AppTaskInfo.Create(title, subtitle, deepLink, iconUri, content)</code> — the task appears on the taskbar immediately. Tasks survive app restarts; the Shell owns the lifecycle. Use <code class="language-plaintext highlighter-rouge">FindAll()</code> to recover them on next launch, <code class="language-plaintext highlighter-rouge">Update()</code> to change state and content, <code class="language-plaintext highlighter-rouge">Remove()</code> when you’re done. The <code class="language-plaintext highlighter-rouge">HiddenByUser</code> property tells you if the user dismissed it.</p>

<p><strong><a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.shell.tasks.apptaskcontent?view=winrt-28000"><code class="language-plaintext highlighter-rouge">AppTaskContent</code></a></strong> controls what the user sees. It has factory methods for different visual representations:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CreateSequenceOfSteps</code> — step-by-step progress (the screenshot above). Takes an array of completed steps and the current step string.</li>
  <li><code class="language-plaintext highlighter-rouge">CreatePreviewThumbnail</code> — a thumbnail preview of the output</li>
  <li><code class="language-plaintext highlighter-rouge">CreateTextSummaryResult</code> — a text summary when the task completes</li>
  <li><code class="language-plaintext highlighter-rouge">CreateGeneratedAssetsResult</code> — display generated files/assets as <code class="language-plaintext highlighter-rouge">AppTaskResultAsset</code> objects</li>
</ul>

<p>You can also bolt on interactive elements: <code class="language-plaintext highlighter-rouge">SetQuestion</code> to ask the user something, <code class="language-plaintext highlighter-rouge">AddButton</code> for deep-link actions, <code class="language-plaintext highlighter-rouge">SetTextInput</code> for free-form input with a URI template (<code class="language-plaintext highlighter-rouge">{userTextInput}</code> placeholder). The Shell renders all of it.</p>

<p><strong><a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.shell.tasks.apptaskstate?view=winrt-28000"><code class="language-plaintext highlighter-rouge">AppTaskState</code></a></strong> is the enum: <code class="language-plaintext highlighter-rouge">Running</code>, <code class="language-plaintext highlighter-rouge">Paused</code>, <code class="language-plaintext highlighter-rouge">Completed</code>, <code class="language-plaintext highlighter-rouge">Error</code>, <code class="language-plaintext highlighter-rouge">NeedsAttention</code>. Straightforward.</p>

<p><strong><a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.shell.tasks.apptaskresultasset?view=winrt-28000"><code class="language-plaintext highlighter-rouge">AppTaskResultAsset</code></a></strong> represents a file or artifact the task produced.</p>

<h2 id="show-me-the-code">Show me the code</h2>

<p>Here’s a basic flow: create a task, update it as your agent progresses, mark it complete.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Windows.UI.Shell.Tasks</span><span class="p">;</span>

<span class="c1">// Create the task content — a sequence of steps the agent will work through</span>
<span class="kt">var</span> <span class="n">content</span> <span class="p">=</span> <span class="n">AppTaskContent</span><span class="p">.</span><span class="nf">CreateSequenceOfSteps</span><span class="p">(</span>
    <span class="k">new</span> <span class="kt">string</span><span class="p">[]</span> <span class="p">{</span> <span class="p">},</span>  <span class="c1">// no completed steps yet</span>
    <span class="s">"Beginning research"</span>  <span class="c1">// current step</span>
<span class="p">);</span>

<span class="c1">// Create the task — it appears on the taskbar immediately</span>
<span class="kt">var</span> <span class="n">task</span> <span class="p">=</span> <span class="n">AppTaskInfo</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span>
    <span class="n">title</span><span class="p">:</span> <span class="s">"Researching retail personalization trends"</span><span class="p">,</span>
    <span class="n">subtitle</span><span class="p">:</span> <span class="s">"Copilot"</span><span class="p">,</span>
    <span class="n">deepLink</span><span class="p">:</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"myapp://tasks/research-123"</span><span class="p">),</span>  <span class="c1">// where to go when clicked</span>
    <span class="n">iconUri</span><span class="p">:</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"ms-appx:///Assets/AgentIcon.png"</span><span class="p">),</span>
    <span class="n">content</span><span class="p">:</span> <span class="n">content</span><span class="p">);</span>
</code></pre></div></div>

<p>As your agent progresses, update the content:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Agent finished step 1, now on step 2</span>
<span class="kt">var</span> <span class="n">updatedContent</span> <span class="p">=</span> <span class="n">AppTaskContent</span><span class="p">.</span><span class="nf">CreateSequenceOfSteps</span><span class="p">(</span>
    <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="s">"Beginning research"</span> <span class="p">},</span>  <span class="c1">// completed steps</span>
    <span class="s">"Searching databases"</span>  <span class="c1">// current step</span>
<span class="p">);</span>
<span class="n">task</span><span class="p">.</span><span class="nf">Update</span><span class="p">(</span><span class="n">AppTaskState</span><span class="p">.</span><span class="n">Running</span><span class="p">,</span> <span class="n">updatedContent</span><span class="p">);</span>
</code></pre></div></div>

<p>When the agent finishes, switch to a result view:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Show generated assets when done</span>
<span class="kt">var</span> <span class="n">assets</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AppTaskResultAsset</span><span class="p">[]</span>
<span class="p">{</span>
    <span class="k">new</span> <span class="nf">AppTaskResultAsset</span><span class="p">(</span>
        <span class="s">"Research Report"</span><span class="p">,</span> <span class="s">"12 sources, 3 databases"</span><span class="p">,</span>
        <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"ms-appx:///Assets/DocIcon.png"</span><span class="p">),</span>
        <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"myapp://reports/research-123"</span><span class="p">))</span>
<span class="p">};</span>
<span class="n">task</span><span class="p">.</span><span class="nf">Update</span><span class="p">(</span><span class="n">AppTaskState</span><span class="p">.</span><span class="n">Completed</span><span class="p">,</span>
    <span class="n">AppTaskContent</span><span class="p">.</span><span class="nf">CreateGeneratedAssetsResult</span><span class="p">(</span><span class="n">assets</span><span class="p">));</span>
</code></pre></div></div>

<p>If the agent needs user input mid-task:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The content model supports interactive elements</span>
<span class="kt">var</span> <span class="n">content</span> <span class="p">=</span> <span class="n">AppTaskContent</span><span class="p">.</span><span class="nf">CreateSequenceOfSteps</span><span class="p">(</span>
    <span class="k">new</span><span class="p">[]</span> <span class="p">{</span> <span class="s">"Beginning research"</span> <span class="p">},</span> <span class="s">"Waiting for input"</span><span class="p">);</span>
<span class="n">content</span><span class="p">.</span><span class="nf">SetQuestion</span><span class="p">(</span><span class="s">"Should I include competitor analysis?"</span><span class="p">);</span>
<span class="n">content</span><span class="p">.</span><span class="nf">AddButton</span><span class="p">(</span><span class="s">"Yes, include it"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"myapp://task/confirm"</span><span class="p">));</span>
<span class="n">content</span><span class="p">.</span><span class="nf">AddButton</span><span class="p">(</span><span class="s">"No, skip it"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">"myapp://task/skip"</span><span class="p">));</span>
<span class="n">content</span><span class="p">.</span><span class="nf">SetTextInput</span><span class="p">(</span><span class="s">"Or type specific instructions"</span><span class="p">,</span>
    <span class="s">"myapp://task/input?text={userTextInput}"</span><span class="p">);</span>
<span class="n">task</span><span class="p">.</span><span class="nf">Update</span><span class="p">(</span><span class="n">AppTaskState</span><span class="p">.</span><span class="n">NeedsAttention</span><span class="p">,</span> <span class="n">content</span><span class="p">);</span>
</code></pre></div></div>

<p>Tasks survive app restarts — the Shell owns the lifecycle. On next launch, find and resume them:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">existingTasks</span> <span class="p">=</span> <span class="n">AppTaskInfo</span><span class="p">.</span><span class="nf">FindAll</span><span class="p">();</span>
<span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">t</span> <span class="k">in</span> <span class="n">existingTasks</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Resume, update, or remove stale tasks</span>
    <span class="c1">// t.HiddenByUser tells you if the user dismissed it</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When the task is no longer relevant:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">task</span><span class="p">.</span><span class="nf">Remove</span><span class="p">();</span>
</code></pre></div></div>

<h2 id="requirements">Requirements</h2>

<p>Two things your app needs:</p>

<ol>
  <li>
    <p><strong>Package identity.</strong> Your app needs package identity so the Shell can associate tasks with it. This doesn’t mean you need a full MSIX package — you can use <a href="https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps">packaging with external location</a> (sparse packaging) to add identity to your existing app without changing how you install or deploy it.</p>

    <p><strong>Alternative for unpackaged agents:</strong> If your agent can’t easily get package identity (e.g. a Python script, a CLI tool), you can build a small packaged helper app whose sole job is to call the AppTasks APIs on behalf of your agent. Your agent talks to the helper via IPC or command-line args; the helper handles the Shell integration.</p>
  </li>
  <li>
    <p><strong>AppExtension manifest entry.</strong> Declare <code class="language-plaintext highlighter-rouge">com.microsoft.apptaskprovider</code> in your Package.appxmanifest:</p>
  </li>
</ol>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Extensions&gt;</span>
  <span class="nt">&lt;uap3:Extension</span> <span class="na">Category=</span><span class="s">"windows.appExtension"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;uap3:AppExtension</span>
      <span class="na">Name=</span><span class="s">"com.microsoft.apptaskprovider"</span>
      <span class="na">PublicFolder=</span><span class="s">"Public"</span>
      <span class="na">Id=</span><span class="s">"MyApp.AppTaskProvider"</span>
      <span class="na">DisplayName=</span><span class="s">"AppTaskProvider for MyApp"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/uap3:Extension&gt;</span>
<span class="nt">&lt;/Extensions&gt;</span>
</code></pre></div></div>

<p>That’s how Windows knows your app provides this kind of status.</p>

<h2 id="why-this-matters-for-agent-developers">Why this matters for agent developers</h2>

<p>The API is marked <code class="language-plaintext highlighter-rouge">[Experimental]</code> and started rolling out to Windows 11 in May 2026. It’s early. But the design pattern here is the one that will stick.</p>

<p>Background agents need a presence in the OS. Not a notification that fires and disappears. Not a toast that gets swiped away. A persistent, glanceable status that the user can check without context-switching.</p>

<p>We’ve had this pattern for decades with download managers and file transfers. Agents are long-running background work. The taskbar is where users already look for that.</p>

<p>The content model is flexible enough to handle the range of agent behaviors: step-by-step progress for research tasks, thumbnails for image generation, asset lists for multi-file output, and interactive elements when the agent needs human input. That last one is probably the most interesting — agents that can ask questions through the Shell UI don’t need to yank you out of whatever you’re doing just to answer “yes” or “no.”</p>

<h2 id="whats-next">What’s next</h2>

<p>If you’re building agents on Windows, start with the <a href="https://learn.microsoft.com/en-us/uwp/api/windows.ui.shell.tasks?view=winrt-28000">API docs</a>. The surface area is small enough to learn in an afternoon, and you get your agent’s status right there on the taskbar instead of making users babysit it.</p>

<p>The broader story — how Windows is becoming a platform where agents are native citizens of the OS — is still unfolding. AppTasks is one piece of that. More to come.</p>

<hr />

<p><strong>Connect with me on LinkedIn to discuss platform architecture for AI agents:</strong> <a href="https://www.linkedin.com/in/asklar/">linkedin.com/in/asklar</a></p>]]></content><author><name>Alexander Sklar</name></author><category term="Windows" /><category term="AI" /><category term="Agentic-Systems" /><category term="windows-11" /><category term="apptasks" /><category term="winrt" /><category term="agents" /><category term="platform-architecture" /><summary type="html"><![CDATA[The Windows.UI.Shell.Tasks API is a simple API that lets apps put long-running agent progress on the taskbar.]]></summary></entry><entry><title type="html">Someone’s Running 40+ Scripts on My Laptop. It’s Not Me.</title><link href="https://asklar.dev/tools/windows/security/2026/07/20/script-warden-analysis.html" rel="alternate" type="text/html" title="Someone’s Running 40+ Scripts on My Laptop. It’s Not Me." /><published>2026-07-20T00:00:00-07:00</published><updated>2026-07-20T00:00:00-07:00</updated><id>https://asklar.dev/tools/windows/security/2026/07/20/script-warden-analysis</id><content type="html" xml:base="https://asklar.dev/tools/windows/security/2026/07/20/script-warden-analysis.html"><![CDATA[<p>Two weeks ago I shipped <a href="/tools/windows/security/2026/07/03/script-warden/">script-warden</a>, a tool that records every script launch on a Windows machine. Most of the responses boiled down to: <em>“wait, how many things are running on my laptop?”</em></p>

<p>I wanted an actual answer. Not “probably some Intune stuff” but a real breakdown: <em>who</em> launched each script and <em>what it does</em>.</p>

<p>So I built an analysis engine into script-warden and pointed it at two weeks of captures on my corporate machine.</p>

<h2 id="classifying-the-noise">Classifying the noise</h2>

<p>The new <code class="language-plaintext highlighter-rouge">analyze</code> command groups every captured script along two axes:</p>

<ol>
  <li><strong>Source</strong> — <em>who</em> launched it. Management agents (Intune/MDM, ConfigMgr), scheduled tasks, Group Policy, EDR/AV, dev tools, Windows servicing.</li>
  <li><strong>Behavior</strong> — <em>what</em> it does. Install/uninstall, update, inventory, config change, persistence, network/download, remote execution, credential access, obfuscation.</li>
</ol>

<p><img src="/assets/images/posts/script-warden/script-warden-analysis-source.png" alt="Analysis grouped by source — who's launching scripts on your machine" /></p>

<p>On my machine, the biggest script launchers weren’t things I run. They were management agents and scheduled tasks I never configured. Background inventory sweeps. Compliance checks. Telemetry collectors. All silent.</p>

<p>None of it is malicious. But “not malicious” and “I knew about this” are not the same thing.</p>

<h2 id="what-are-they-actually-doing">What are they actually doing?</h2>

<p><img src="/assets/images/posts/script-warden/script-warden-analysis-behavior.png" alt="Analysis grouped by behavior — what those scripts are doing" /></p>

<p>The behavior breakdown is where it got interesting. The single biggest category was <strong>inventory</strong>: scripts that scan your hardware, installed software, and registry state, then report it somewhere. Others silently adjust settings. A few do network/download operations you’d never notice.</p>

<p>Probably fine. But “probably fine” looks different when you can see the actual script.</p>

<p><img src="/assets/images/posts/script-warden/script-warden-script-view.png" alt="A ConfigMgr VPN profile script caught by script-warden — full launch chain, syntax-highlighted, decoded" /></p>

<p>This one is a PowerShell script launched by ConfigMgr through WMI to check whether my VPN profile version matches what IT expects. I didn’t run it. I didn’t know it existed. It ran for 4.7 seconds and exited silently. script-warden caught it, decoded it, and showed me the full parent chain: <code class="language-plaintext highlighter-rouge">wininit.exe</code> all the way down to <code class="language-plaintext highlighter-rouge">powershell.exe</code>.</p>

<h2 id="transparency-is-not-a-security-risk">Transparency is not a security risk</h2>

<p>If a script is safe to run on my machine, it’s safe for me to read. Enterprise tooling running hidden with no user-accessible audit trail is a UX choice, not a security requirement.</p>

<p>Your laptop slow at 2pm every Tuesday? Now you can find out why. That PowerShell window that flashes for a split second? You can read what it did. The base64-encoded command that ran at 3am? Decoded and saved.</p>

<h2 id="composable-filters">Composable filters</h2>

<p>The analysis view is a drill-down tool. Stack filters (taxonomy include/exclude, time range, full-text content search), see the live match count, then click through to matching events. Search for a URL, a registry key, or an executable name across every script that ever ran on your machine.</p>

<p>Taxonomies are JSON files. Edit them, drop in your own categories. script-warden auto-updates the built-ins but never touches your custom ones.</p>

<h2 id="whats-next">What’s next</h2>

<p>The captures are structured data with full script content. That means you can point a model at them. I’m working on an AI analysis mode that reasons over your capture history to surface patterns, flag anomalies, and answer questions like “what ran differently this week vs. last week?” or “show me everything that touches the network after midnight.” The data is already there. It just needs a brain on top.</p>

<h2 id="try-it">Try it</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">script-warden</span><span class="w"> </span><span class="nx">install</span><span class="w">       </span><span class="c"># start monitoring (one-time admin prompt)</span><span class="w">
</span><span class="c"># ... let it collect for a day or two ...</span><span class="w">
</span><span class="n">script-warden</span><span class="w"> </span><span class="nx">analyze</span><span class="w">       </span><span class="c"># build the analysis database</span><span class="w">
</span><span class="n">script-warden</span><span class="w"> </span><span class="nx">serve</span><span class="w">         </span><span class="c"># browse everything in the web viewer</span><span class="w">
</span></code></pre></div></div>

<p>The Analysis tab in the viewer uses the same engine. Click any category to filter the full audit log to matching entries.</p>

<p><strong><a href="https://github.com/asklar/script-warden">github.com/asklar/script-warden</a></strong> — MIT licensed, single exe, no runtime.</p>

<p>Point it at your machine. You should know what’s running on it.</p>

<hr />

<p><em>What would you find on yours? Run <code class="language-plaintext highlighter-rouge">script-warden install</code> and find out. If you do, I’d love to hear what surprises you — <a href="https://www.linkedin.com/in/asklar/">find me on LinkedIn</a> or <a href="https://github.com/asklar/script-warden/issues">open an issue</a>.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="Tools" /><category term="Windows" /><category term="Security" /><category term="windows" /><category term="security" /><category term="dotnet" /><category term="powershell" /><category term="devtools" /><summary type="html"><![CDATA[I ran script-warden on my corporate laptop for two weeks and classified every script by who launched it and what it does.]]></summary></entry><entry><title type="html">What Scripts Is Your IT Department Running on Your Machine?</title><link href="https://asklar.dev/tools/windows/security/2026/07/03/script-warden.html" rel="alternate" type="text/html" title="What Scripts Is Your IT Department Running on Your Machine?" /><published>2026-07-03T00:00:00-07:00</published><updated>2026-07-03T00:00:00-07:00</updated><id>https://asklar.dev/tools/windows/security/2026/07/03/script-warden</id><content type="html" xml:base="https://asklar.dev/tools/windows/security/2026/07/03/script-warden.html"><![CDATA[<p>A console window kept flashing on my screen. A dark rectangle, a fraction of a second, gone. Every so often, no pattern I could pin down.</p>

<p>I tried to catch it. Task Manager wasn’t fast enough. Event logs told me <em>that</em> something ran, sometimes, but never handed me the actual script. On a managed Windows machine, that’s the norm: logon scripts, config-management agents, scheduled tasks, and remote-management tooling shell out to PowerShell and <code class="language-plaintext highlighter-rouge">cmd</code> dozens of times a day, usually with no window, usually gone in milliseconds, occasionally as a base64 blob you couldn’t read even if you caught it.</p>

<p>None of it is sinister. Most of it is patch management and compliance checks doing exactly what they should. But “probably fine” is a strange thing to accept about code you can’t see. I wanted to <em>read</em> it.</p>

<p>So I built <strong><a href="https://github.com/asklar/script-warden">script-warden</a></strong>: a single Windows executable that records every script launch, keeps a copy of the actual script, and lets you browse the whole trail in a local web viewer. It’s open source (MIT), and this post is about the itch and the one genuinely clever trick that makes it work.</p>

<h2 id="the-observability-gap">The Observability Gap</h2>

<p>The problem was never that scripts run. It’s that they run <em>and disappear</em>.</p>

<p>The requirements more or less wrote themselves:</p>

<ul>
  <li>Record <strong>every</strong> launch of the usual interpreters: <code class="language-plaintext highlighter-rouge">powershell.exe</code>, <code class="language-plaintext highlighter-rouge">pwsh.exe</code>, <code class="language-plaintext highlighter-rouge">cmd.exe</code>, <code class="language-plaintext highlighter-rouge">cscript.exe</code>, <code class="language-plaintext highlighter-rouge">wscript.exe</code>.</li>
  <li>Keep the <strong>actual script</strong>: copy referenced <code class="language-plaintext highlighter-rouge">.ps1</code> / <code class="language-plaintext highlighter-rouge">.bat</code> / <code class="language-plaintext highlighter-rouge">.cmd</code> / <code class="language-plaintext highlighter-rouge">.vbs</code> / <code class="language-plaintext highlighter-rouge">.js</code> files, save inline <code class="language-plaintext highlighter-rouge">-Command</code> / <code class="language-plaintext highlighter-rouge">cmd /c</code> text, and <em>decode</em> <code class="language-plaintext highlighter-rouge">-EncodedCommand</code> payloads into something readable. Everything content-addressed and de-duplicated by SHA-256.</li>
  <li>Note whether each ran <strong>Visible</strong> (had a console) or <strong>Hidden</strong> (no window / background), because the silent ones are exactly the ones you want to see.</li>
  <li>Capture the full <strong>parent chain</strong>, so you can trace a launch from the script up to the scheduled-task host or management agent that started it. It reads SYSTEM-level data too, so scripts that ran with full privileges show up.</li>
  <li>Never, ever break the thing it’s watching. If the audit logic throws, PowerShell must still run.</li>
</ul>

<p>That last point turned out to shape the whole design.</p>

<h2 id="the-one-clever-trick">The One Clever Trick</h2>

<p>How do you sit in front of <em>every</em> PowerShell launch on a machine with no kernel driver, no OS patching, and no fragile global hook?</p>

<p>Windows has a decades-old debugging feature called <strong><a href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xperf/image-file-execution-options">Image File Execution Options</a></strong> (IFEO). Register a <code class="language-plaintext highlighter-rouge">Debugger</code> value for an executable name, and from then on, whenever Windows is asked to launch <code class="language-plaintext highlighter-rouge">powershell.exe</code>, it instead launches <em>your</em> program with the original PowerShell command line handed to you as arguments. It’s the same supported mechanism used to attach a debugger at process start. script-warden registers itself as that debugger.</p>

<p>That’s the hook, and it creates an obvious problem. If my tool’s job is to relaunch the real PowerShell, and every launch of PowerShell is intercepted by my tool… I’ve built an infinite loop that takes down every shell on the machine. Blast radius: the entire OS.</p>

<p>The escape hatch is a lovely little detail in the loader. When you start a process with the <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags">DEBUG_ONLY_THIS_PROCESS</a> flag, Windows <strong>skips the IFEO redirection</strong> for that launch. So script-warden relaunches the real interpreter <em>as if it were debugging it</em>, immediately detaches, and lets it run with completely normal semantics: same stdio, working directory, environment, exit code, and the original command line forwarded <em>verbatim</em>, quoting and all. That last part matters more than it sounds: a tool whose promise is “I recorded exactly what ran” can’t paraphrase. To the user and to every script, nothing happened. There’s just now a durable, faithful record of it.</p>

<h2 id="making-the-data-worth-having">Making the Data Worth Having</h2>

<p>Capturing is only half of it. A folder of a thousand JSON files isn’t insight. So <code class="language-plaintext highlighter-rouge">script-warden serve</code> opens a local web viewer (React + Fluent UI, embedded straight into the executable, so it’s still a single file). You get a fast, paginated, searchable trail filtered by interpreter, origin, parent process, and <strong>visibility</strong>; the full launch chain for any event; and the captured script itself to view or download, including the decoded version of that base64 blob.</p>

<p><img src="/assets/images/posts/script-warden/script-warden-list.png" alt="script-warden audit log: every script launch with interpreter, origin, visibility, parent process, and duration" /></p>

<p>Click any entry to see the full detail: command line, visibility, parent chain, working directory, and the captured scripts themselves.</p>

<p><img src="/assets/images/posts/script-warden/script-warden-detail.png" alt="Detail view: command line, launch chain, and captured scripts with View/Download" /></p>

<p>There’s a deliberate principle running through it: <strong>talk about what the user cares about, not the plumbing.</strong> You don’t filter by “window-handle state”; you filter by <em>Visible</em> vs <em>Hidden</em>. The interesting engineering lives in the code; the product speaks in your questions.</p>

<h2 id="why-native-aot">Why Native AOT</h2>

<p>This thing runs on <em>every single interpreter launch</em> on the machine. If it added a few hundred milliseconds of CLR startup each time, I’d uninstall my own tool within a day. <a href="https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/">.NET Native AOT</a> (ahead-of-time compilation) compiles the whole program to a single native executable: around 4 MB, starts in a blink, no .NET runtime to install or ship. The constraint enforced discipline, too: all P/Invoke uses <a href="https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation">LibraryImport</a> source generators (compile-time marshalling, no runtime reflection), all JSON uses <a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation">System.Text.Json source generators</a> (same idea for serialization), and the whole thing publishes AOT-clean. Constraints like these tend to make software better, not worse.</p>

<h2 id="try-it">Try It</h2>

<p>script-warden is MIT licensed and lives here: <strong><a href="https://github.com/asklar/script-warden">github.com/asklar/script-warden</a></strong>.</p>

<p>It’s a single self-contained <code class="language-plaintext highlighter-rouge">.exe</code>. Run <code class="language-plaintext highlighter-rouge">script-warden diagnose</code> first. It self-tests your data locations, current monitoring state, and simulates a capture so you can see exactly what it does before you commit. Then <code class="language-plaintext highlighter-rouge">install</code> starts monitoring (machine-wide, so it prompts for admin once), <code class="language-plaintext highlighter-rouge">serve</code> opens the viewer, and <code class="language-plaintext highlighter-rouge">uninstall</code> cleanly removes only what it added.</p>

<p>Point it at your own machine and go read what’s been running. You might be surprised.</p>

<hr />

<p><em>I write about building with AI agents and the deep Windows internals that make them possible: the stuff that works and the stuff that breaks. <a href="https://www.linkedin.com/in/asklar/">Follow me on LinkedIn</a> for more.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="Tools" /><category term="Windows" /><category term="Security" /><category term="windows" /><category term="security" /><category term="dotnet" /><category term="native-aot" /><category term="powershell" /><category term="devtools" /><summary type="html"><![CDATA[On a managed Windows machine, dozens of scripts run silently every day. You've never seen them. Now you can.]]></summary></entry><entry><title type="html">I Built a World Cup Map as a Copilot Canvas Extension</title><link href="https://asklar.dev/ai/tools/2026/06/17/world-cup-canvas.html" rel="alternate" type="text/html" title="I Built a World Cup Map as a Copilot Canvas Extension" /><published>2026-06-17T00:00:00-07:00</published><updated>2026-06-17T00:00:00-07:00</updated><id>https://asklar.dev/ai/tools/2026/06/17/world-cup-canvas</id><content type="html" xml:base="https://asklar.dev/ai/tools/2026/06/17/world-cup-canvas.html"><![CDATA[<p>The 2026 World Cup is happening across 16 stadiums in the US, Canada, and Mexico. I kept opening fixture lists, squinting at time zones, losing track of which venue was where. So I built a map — as a GitHub Copilot canvas extension.</p>

<p>But the interesting part isn’t the map. It’s how I use it.</p>

<video autoplay="" loop="" muted="" playsinline="" style="width:100%; border-radius:8px;">
  <source src="/assets/images/posts/world-cup-canvas/demo.mp4" type="video/mp4" />
</video>

<h2 id="what-makes-it-a-canvas">What makes it a canvas</h2>

<p>A <a href="https://docs.github.com/en/copilot/how-tos/github-copilot-app/working-with-canvas-extensions">“canvas”</a> in Copilot is a UI that both you and the agent can see and manipulate at the same time. Think of it like a shared screen — except one of the people driving is an AI. The canvas renders in the <a href="https://github.blog/news-insights/product-news/github-copilot-app-the-agent-native-desktop-experience/">Copilot desktop app</a>’s side panel, but since it’s just a local web server under the hood, you can also open it in any browser. I did all my development from Copilot CLI and just pointed Edge at <code class="language-plaintext highlighter-rouge">localhost:60698</code>.</p>

<p>My extension shows a <a href="https://leafletjs.com">Leaflet</a> + <a href="https://www.openstreetmap.org">OpenStreetMap</a> map with a pin for every match. Green for finished results, orange for upcoming fixtures. Click a pin and you get team badges, scores or kickoff times, venue, round, and a link out to highlights on YouTube. You can filter by status or team.</p>

<p>I can click around the map myself. Or I can tell the agent “focus on Mexico’s next match” and it pans the map for me. Same UI, two ways to drive it.</p>

<h2 id="the-architecture">The Architecture</h2>

<p>The extension runs a Node HTTP + SSE server that serves both the map UI and a <code class="language-plaintext highlighter-rouge">/api/matches</code> endpoint. The agent has three canvas actions it can invoke:</p>

<ul>
  <li><strong>refresh_matches</strong> — pull latest data and update all markers</li>
  <li><strong>focus_match</strong> — pan/zoom to a specific match</li>
  <li><strong>filter_matches</strong> — show only matches matching criteria (team, status, round)</li>
</ul>

<p>These push live updates over Server-Sent Events. The canvas in GitHub Copilot’s desktop side panel receives them and re-renders instantly.</p>

<p><img src="/assets/images/posts/world-cup-canvas/canvas-sse-architecture.svg" alt="Canvas SSE Architecture" /></p>

<p>Plain CLI tools (<code class="language-plaintext highlighter-rouge">worldcup_list_matches</code>, <code class="language-plaintext highlighter-rouge">worldcup_match_detail</code>) also exist so the agent can answer match questions even with the canvas closed. The canvas is the rich interface; the CLI is the fallback.</p>

<h2 id="data-wrangling">Data Wrangling</h2>

<p>Match data comes from <a href="https://www.thesportsdb.com">TheSportsDB</a> (league 4429, season 2026). Getting all 72 group-stage matches required merging three separate endpoints — events by round, next scheduled, and past results — then deduping by match ID. No single endpoint returns everything.</p>

<p>TheSportsDB events have no coordinates. I built a static lookup table mapping 16 host stadiums to lat/lng, with name aliases to handle variations (“Reliant Stadium” → NRG Stadium, “BC Place Stadium” → BC Place).</p>

<h2 id="how-i-actually-built-it">How I actually built it</h2>

<p>Here’s my entire contribution to the initial build. I opened Copilot CLI and typed:</p>

<blockquote>
  <p>“I’m a big soccer fan and it’s currently the FIFA world cup, I’d like to build a copilot canvas extension that shows me the matches’ results and upcoming games, in a map control”</p>
</blockquote>

<p>That’s it. One sentence. The agent spent the next 107 minutes figuring out the rest — spelunking the SDK’s <code class="language-plaintext highlighter-rouge">.d.ts</code> files to discover the canvas API, researching TheSportsDB endpoints, writing the Node server, wiring up SSE, building the Leaflet map, and handling all the coordinate lookups. I rejected one tool call (“no codebase yet, you’ll start from scratch”) and otherwise just watched.</p>

<p>After that, I spent maybe 20 more minutes iterating: fixing the two bugs I describe below, adding the team filter, tweaking the popup styling. But the core architecture — the SSE flow, the canvas actions, the data merging — all came from that one prompt.</p>

<h2 id="what-the-agent-figured-out">What the agent figured out</h2>

<p>Watching the agent work through problems in real time was half the fun:</p>

<p><strong>“Only results, no upcoming matches.”</strong> The agent hit a wall: TheSportsDB’s past-events endpoint only returns finished matches, and the next-events endpoint only returns scheduled ones. Neither returns both. Its fix: merge all three feeds, dedupe on ID. I just said “there should be upcoming games too” and it root-caused the data source on its own.</p>

<p><strong>“Canvas server doesn’t seem to be running.”</strong> Each time the extension reloaded during development, it bound a new ephemeral port — orphaning any canvas tab still pointing at the old one. The agent figured out the fix: bind a stable port (60698) with <code class="language-plaintext highlighter-rouge">EADDRINUSE</code> fallback so the canvas URL stays constant across reloads.</p>

<p><strong>Discovering the canvas API itself.</strong> Canvas extensions aren’t heavily documented yet. The agent found <code class="language-plaintext highlighter-rouge">createCanvas</code> and <code class="language-plaintext highlighter-rouge">joinSession</code> by spelunking the SDK’s bundled <code class="language-plaintext highlighter-rouge">.d.ts</code> type definition files. The types were the map.</p>

<h2 id="try-it">Try It</h2>

<p>The extension is <a href="https://github.com/asklar/worldcup-map-canvas-2026">open source on GitHub</a>. Install by cloning into your Copilot extensions directory:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># User scope (just you)</span>
git clone https://github.com/asklar/worldcup-map-canvas-2026 ~/.copilot/extensions/worldcup-map

<span class="c"># Team scope (shared repo)</span>
git clone https://github.com/asklar/worldcup-map-canvas-2026 .github/extensions/worldcup-map
</code></pre></div></div>

<p>It works in the GitHub Copilot desktop app or any browser pointed at the local server.</p>

<p>The World Cup runs through July 19. The map updates live. And if I want to know when Uruguay plays next — I just ask.</p>

<hr />

<p><em>I write about building with AI agents, the stuff that actually works and the stuff that breaks. <a href="https://www.linkedin.com/in/asklar/">Follow me on LinkedIn</a> for more.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="AI" /><category term="Tools" /><category term="copilot" /><category term="canvas" /><category term="extensions" /><category term="world-cup" /><summary type="html"><![CDATA[An interactive Leaflet map showing every 2026 FIFA World Cup match — built as a GitHub Copilot canvas extension where human and agent share the same live surface.]]></summary></entry><entry><title type="html">MXC: The Missing Piece for Agent Containment on Windows</title><link href="https://asklar.dev/ai/engineering/2026/06/03/mxc-agent-containment.html" rel="alternate" type="text/html" title="MXC: The Missing Piece for Agent Containment on Windows" /><published>2026-06-03T00:00:00-07:00</published><updated>2026-06-03T00:00:00-07:00</updated><id>https://asklar.dev/ai/engineering/2026/06/03/mxc-agent-containment</id><content type="html" xml:base="https://asklar.dev/ai/engineering/2026/06/03/mxc-agent-containment.html"><![CDATA[<p>Every agent framework today has the same default behavior: your agent runs with your full permissions. It can read your SSH keys, your browser cookies, your tax returns. Not because it needs to — because nobody told it not to.</p>

<p>That’s the fundamental gap Microsoft Execution Containers (MXC) fills — and it’s not theoretical for me. My team and I have been integrating MXC into OpenClaw so agents can run tools locally without inheriting a full user session.</p>

<p>To make the idea concrete, here’s the mental model I use:</p>

<figure>
  <img src="/assets/images/posts/mxc-agent-containment/header.svg" alt="The agent containment spectrum: no isolation, monolithic boundary, MXC containment" />
  <figcaption>The containment spectrum: from “trust everything,” to monolithic one-size-fits-all boundaries, to composable, policy-driven isolation.</figcaption>
</figure>

<p>Announced at <a href="https://blogs.windows.com/windowsdeveloper/2026/06/02/build-2026-furthering-windows-as-the-trusted-platform-for-development/">Build 2026</a>, MXC is a policy-driven execution layer that lets developers declare what an agent can access — files, network, system resources — and has the OS enforce those boundaries at runtime. It’s <a href="https://github.com/microsoft/mxc">open source on GitHub</a> and works across <strong>Windows, Linux, and macOS</strong>.</p>

<figure>
  <img src="/assets/images/posts/mxc-agent-containment/satya-mxc-keynote.jpg" alt="Build keynote slide announcing Microsoft Execution Containers (MXC)" />
  <figcaption>Build keynote slide announcing Microsoft Execution Containers (MXC). Source: Microsoft Build keynote highlights (2026). </figcaption>
</figure>

<h2 id="the-problem-trust-without-boundaries">The Problem: Trust Without Boundaries</h2>

<p>Right now, when you run a coding agent, it inherits your session. Every file, every credential, every network path.</p>

<p>If you can access it, so can the agent.</p>

<p>That’s super powerful.</p>

<p>And that’s where it gets scary: the agent can mess up or overstep, at machine speed.</p>

<p>The agent might be well-behaved, but the model generating its actions is non-deterministic. One hallucinated command, one prompt injection, and your agent is doing things you never intended with access you never meant to grant.</p>

<p>Traditional containment often collapses into a monolithic boundary: either you grant broad access so the agent can work, or you lock it down so hard it becomes useless. Agents need something in between.</p>

<h2 id="what-mxc-actually-does">What MXC Actually Does</h2>

<p>MXC introduces a <strong>containment spectrum</strong> — not a single sandbox, but composable isolation that scales with risk:</p>

<ul>
  <li><strong>Process isolation</strong> — restrict what a single agent process can touch</li>
  <li><strong>Filesystem boundaries</strong> — declare which paths are readable, writable, or invisible</li>
  <li><strong>Network constraints</strong> — control what an agent can reach over the wire</li>
  <li><strong>Composable policies</strong> — layer isolation primitives based on workload risk, not one-size-fits-all rules</li>
</ul>

<p>The key insight is that a coding agent and an enterprise data-processing agent don’t need the same guardrails, but they do need one coherent trust model.</p>

<p>More specifically: you want to be able to apply containment <em>per agent</em> (or per agent instance / task), not just “per framework.” MXC provides the abstraction layer so developers define intent (“this agent reads project files and nothing else”) and the OS handles enforcement.</p>

<h2 id="why-this-matters-for-agent-builders">Why This Matters for Agent Builders</h2>

<p>If you’re building agents today, you’re probably doing one of three things for security:</p>

<ol>
  <li><strong>Nothing</strong> — trusting the model not to do bad things (most common, most dangerous)</li>
  <li><strong>Containers</strong> (often Docker/VM-shaped) — workable, but heavyweight for the inner loop and frequently ends up feeling like a Linux-first story bolted onto Windows</li>
  <li><strong>Custom sandboxing</strong> — reinventing the wheel per framework</li>
</ol>

<p>MXC replaces all three with a platform primitive. You declare a policy, the OS enforces it. Your agent runs contained without you managing cgroups, namespaces, or AppContainer details.</p>

<p>The <a href="https://blogs.windows.com/windowsdeveloper/2026/06/02/windows-platform-security-for-ai-agents/">Windows security blog post</a> walks through the broader platform story around agent containment and policy-based controls.</p>

<h2 id="the-composable-part-is-the-breakthrough">The Composable Part Is the Breakthrough</h2>

<p>Plenty of systems can sandbox a process. What makes MXC interesting is the composability. You can start with lightweight process isolation during development, then layer on filesystem restrictions for staging, then add network constraints for production — all using the same policy model. The containment adapts to the context without requiring architectural changes.</p>

<p>This is the pattern I keep seeing across successful platform work: don’t force developers to choose between security and capability. Give them a spectrum they can dial based on risk tolerance.</p>

<h2 id="whats-available-now">What’s Available Now</h2>

<p>MXC is in early preview. The SDK is <a href="https://github.com/microsoft/mxc">on GitHub</a> and covers process isolation, filesystem boundaries, and network constraints across <strong>Windows, Linux, and macOS</strong>.</p>

<p>It’s also already landing in real developer workflows:</p>

<ul>
  <li>
    <p><strong>GitHub Copilot local sandboxes</strong> are in public preview and are <em>built on Microsoft MXC technology</em> for a consistent isolation experience across macOS, Linux, and Windows. See: <a href="https://github.blog/changelog/2026-06-02-cloud-and-local-sandboxes-for-github-copilot-now-in-public-preview/">Cloud and local sandboxes for GitHub Copilot now in public preview</a> (use <code class="language-plaintext highlighter-rouge">/sandbox enable</code> to enable sandboxing for a session). In the current preview, this focuses on isolating <strong>shell command execution</strong> initiated by Copilot; MCP servers / language servers are <strong>separately controlled surfaces</strong> from the shell tool sandboxing.</p>

    <p>On Windows, MXC’s default backend targets <strong>Windows 11 24H2+</strong> (per the MXC repo), and some backends are <strong>Insider Preview</strong>-gated.</p>

    <p>Some capabilities are gated behind Copilot CLI’s experimental mode. You can enable it either at launch (<code class="language-plaintext highlighter-rouge">copilot --experimental</code>) or inside a running session (<code class="language-plaintext highlighter-rouge">/experimental</code>). See Copilot CLI docs: https://github.com/github/copilot-cli#experimental-mode</p>

    <p>I’ve also been working in close partnership with the GitHub Copilot CLI team on the practicalities of local sandboxing + tool execution in real developer workflows.</p>
  </li>
  <li>
    <p><strong>OpenClaw on Windows</strong> was announced at Build as running natively on Windows leveraging MXC — the gateway and node processes run contained so your system stays secure out of the box. See: <a href="https://blogs.windows.com/windowsdeveloper/2026/06/02/build-2026-furthering-windows-as-the-trusted-platform-for-development/">Build 2026 Windows platform post</a> and <a href="https://blogs.windows.com/windowsdeveloper/2026/06/02/windows-platform-security-for-ai-agents/">Windows platform security for AI agents</a>.</p>
  </li>
</ul>

<p>If you watched the keynote, this is the kind of capability being positioned as “agent-grade” platform security: not just better prompts, but OS-enforced containment.</p>

<h2 id="the-bigger-picture">The Bigger Picture</h2>

<p>Agent containment is the infrastructure work that determines whether enterprises actually deploy local agents or keep everything locked down in cloud sandboxes. MXC makes the local path viable by giving IT teams the controls they need and giving developers the flexibility they want.</p>

<p>Agents are coming. The question was never whether they’d be powerful — it was whether we could trust them on real systems with real data. MXC is how Windows answers that question.</p>

<h2 id="try-it--help-shape-it">Try it + help shape it</h2>

<p>If you’re building agents and you care about running them locally with real guardrails, go kick the tires:</p>

<ul>
  <li><a href="https://github.com/microsoft/mxc">MXC repo + SDK</a></li>
</ul>

<p>Early previews get better fast when real builders bring real edge cases.</p>

<hr />

<p><em>I write about building with AI agents, the stuff that actually works and the stuff that breaks. <a href="https://www.linkedin.com/in/asklar/">Follow me on LinkedIn</a> for more.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="AI" /><category term="Engineering" /><category term="mxc" /><category term="agents" /><category term="windows" /><category term="security" /><category term="containment" /><category term="build-2026" /><summary type="html"><![CDATA[Microsoft Execution Containers give agents declared boundaries instead of implied trust. Here's why that changes everything for local agent deployment.]]></summary></entry><entry><title type="html">18 Minutes, One Extension, Full Access</title><link href="https://asklar.dev/ai/security/2026/05/27/18-minutes-one-extension.html" rel="alternate" type="text/html" title="18 Minutes, One Extension, Full Access" /><published>2026-05-27T00:00:00-07:00</published><updated>2026-05-27T00:00:00-07:00</updated><id>https://asklar.dev/ai/security/2026/05/27/18-minutes-one-extension</id><content type="html" xml:base="https://asklar.dev/ai/security/2026/05/27/18-minutes-one-extension.html"><![CDATA[<p>A popular AI coding tool called <a href="https://github.com/open-gsd/get-shit-done-redux/discussions/109">GSD (“Get Shit Done”)</a> got rug-pulled this week. The maintainer disappeared, social accounts were deleted, and a crypto token associated with the project was publicly linked to a rug-pull. The community forked the code, but the original npm packages are still out there under the old owner’s control.</p>

<p>The community response was predictable: “be more careful what you install.”</p>

<p>But this isn’t new. Earlier this month, a <a href="https://github.com/nrwl/nx-console/security/advisories/GHSA-c9j4-9m59-847w">compromised VS Code extension</a> was live for 18 minutes. That was enough. The payload harvested credentials from everywhere it could reach — npm tokens, AWS keys, SSH keys, vault tokens, GitHub PATs. One employee had it installed. <a href="https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/">Thousands of internal repos were accessed</a>. Signing keys had to be rotated. Every Enterprise Server customer had to take action.</p>

<p>18 minutes. One extension. One machine.</p>

<p>The extension wasn’t sketchy. It was well-known, widely-used, actively-maintained. The compromise came through a chain of poisoned dependencies. The malicious payload didn’t even need anything exotic — it just called tools already on the machine to grab credentials.</p>

<p>And it’s not just extensions. Earlier this year, <a href="https://notepad-plus-plus.org/news/hijacked-incident-info-update/">Notepad++</a> was hijacked by what security researchers assessed as a Chinese state-sponsored group — not through a code vulnerability, but by compromising the hosting provider and selectively redirecting update traffic. The software was fine. The distribution was poisoned.</p>

<h2 id="mitigations-exist-but-they-dont-solve-this">Mitigations exist, but they don’t solve this</h2>

<p>Yes, there are org-level controls. You can disable the VS Code marketplace entirely. You can restrict extensions to trusted publishers. You can lock down package registries.</p>

<p>But “trusted publisher” wouldn’t have helped here — the compromised extension <em>was</em> from a trusted publisher. The Notepad++ compromise bypassed the publisher entirely. You can lock everything down to be secure and also completely useless. But if you want your developers to be productive, a balance has to be struck — and that’s where the real nuance lives. How much do you allow to run? Which tools get access to what? Who decides?</p>

<p>The advice we keep giving after these incidents — vet your dependencies, check the maintainer, audit your supply chain — is correct. It’s also completely insufficient at scale.</p>

<p>Because the actual problem isn’t which tools you install. It’s what those tools can do once they’re running.</p>

<p>You might assume VS Code extensions run sandboxed. They don’t. Extensions run in a separate extension host process, but according to <a href="https://code.visualstudio.com/docs/configure/extensions/extension-runtime-security">VS Code’s own docs</a>, that process has the same permissions as VS Code itself — it can read and write files, make network requests, and run external processes. Compare that to browser extensions, which run in a sandboxed environment with a declared permission model — they can’t just read arbitrary files off your disk. The browser figured this out years ago. Developer tooling hasn’t caught up.</p>

<p>An npm <code class="language-plaintext highlighter-rouge">postinstall</code> script runs with the same permissions as you. An MCP server runs inside your agent’s process with whatever the agent can touch.</p>

<p>There’s no boundary between “I added a productivity tool” and “that tool can read every secret on my machine.”</p>

<h2 id="the-right-question">The right question</h2>

<p>The question I keep coming back to: why is full trust the default?</p>

<p>Why doesn’t the platform enforce what a tool can actually reach — which files, which network endpoints, which credentials — regardless of whether you trust the maintainer?</p>

<p>Some ecosystems are starting to get this right. <a href="https://docs.deno.com/runtime/fundamentals/security/">Deno</a> ships with <code class="language-plaintext highlighter-rouge">--allow-net</code>, <code class="language-plaintext highlighter-rouge">--allow-read</code>, <code class="language-plaintext highlighter-rouge">--allow-env</code> — you have to explicitly grant each capability. Mobile operating systems figured this out a decade ago: apps declare permissions, the OS enforces them, and you can revoke access at any time. Windows has had this with UWP and AppContainer — sandboxing, declared capabilities, brokered access to resources. The primitives exist. They’re just not being applied to developer tooling yet.</p>

<p>Developer tooling is still stuck in the 2005 model where everything runs with your full permissions and we just hope nobody’s malicious.</p>

<h2 id="what-would-boundaries-as-infrastructure-look-like">What would “boundaries as infrastructure” look like?</h2>

<p>A few things that would have changed the outcome here:</p>

<p><strong>Filesystem isolation.</strong> The extension doesn’t need access to <code class="language-plaintext highlighter-rouge">~/.vault-token</code> or <code class="language-plaintext highlighter-rouge">~/.npmrc</code> or <code class="language-plaintext highlighter-rouge">~/.ssh/</code>. Why can it read them? Restrict tools to the workspace they’re operating on, plus explicitly declared paths.</p>

<p><strong>Network policy.</strong> A code linting extension has no reason to make outbound HTTP requests to arbitrary endpoints. A language server doesn’t need to phone home. Enforce a default-deny network policy and make tools declare what they need.</p>

<p><strong>Credential isolation.</strong> Your GitHub PAT, your AWS keys, your SSH keys — these should not be ambient. Tools should request credential access through a mediated API, not just read files off disk.</p>

<p><strong>Per-invocation sandboxing.</strong> Not “this tool is sandboxed” as a binary yes/no, but different sandbox shapes for different tools based on what they actually need. A filesystem MCP server gets filesystem access to specific paths. A GitHub MCP server gets network access to <code class="language-plaintext highlighter-rouge">api.github.com</code>. A linter gets read access to the workspace and nothing else.</p>

<p>None of this is novel. It’s how mobile platforms have worked for over a decade. It’s how Deno works today. It’s how containers work. We just haven’t applied it to developer workstations yet, because “developers need full access” has been the unquestioned assumption.</p>

<h2 id="be-careful-is-individual-advice-boundaries-are-infrastructure">“Be careful” is individual advice. Boundaries are infrastructure.</h2>

<p>Individual judgment doesn’t scale. You can’t vet every transitive dependency. You can’t audit every extension update. You can’t “be more careful” when a trusted tool gets compromised three hops deep in the dependency chain and the malicious version is live for 18 minutes.</p>

<p>What scales is enforcing boundaries at the platform level — so that even when a tool is compromised, the blast radius is contained to what that tool was supposed to access in the first place, and auditing can happen for forensic purposes.</p>

<p>We have the primitives. Sandboxing, capability-based permissions, network policy enforcement — this isn’t speculative technology. It’s just not being applied where developers live.</p>

<p>That’s the gap. And until we close it, “be careful what you install” will keep being our only answer, and it will keep being insufficient.</p>

<hr />

<p><em>I write about AI agents, developer tooling, and platform security. If this resonated, <a href="https://www.linkedin.com/in/asklar/">connect with me on LinkedIn</a> — I post about these topics regularly.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="AI" /><category term="Security" /><category term="agents" /><category term="supply-chain" /><category term="governance" /><category term="security" /><category term="extensions" /><summary type="html"><![CDATA[A compromised VS Code extension was live for 18 minutes. That was enough. The real problem isn't which tools you install — it's what they can do once they're running.]]></summary></entry><entry><title type="html">I Built a Compiler with Agent Fleets. Here’s What Broke.</title><link href="https://asklar.dev/ai/tools/2026/03/24/agent-fleet-lessons.html" rel="alternate" type="text/html" title="I Built a Compiler with Agent Fleets. Here’s What Broke." /><published>2026-03-24T00:00:00-07:00</published><updated>2026-03-24T00:00:00-07:00</updated><id>https://asklar.dev/ai/tools/2026/03/24/agent-fleet-lessons</id><content type="html" xml:base="https://asklar.dev/ai/tools/2026/03/24/agent-fleet-lessons.html"><![CDATA[<p>I used GitHub Copilot’s <a href="https://docs.github.com/en/copilot/concepts/agents/copilot-cli/fleet">agent fleets</a> to build a compiler. Not a toy — 3,000+ tests, 91 diagnostics, better than what we had before. Multiple agents running in parallel while I did other things.</p>

<p>The throughput is real. But it also broke in ways I didn’t expect.</p>

<p>This post is about those breaks, and the rules I now follow to prevent them.</p>

<h2 id="the-pitch">The pitch</h2>

<p>Agent fleets let you dispatch work and walk away. You describe what you want, a Copilot session spins up, and starts working on your repo. You can run multiple agents in parallel on different tasks. They all operate on the same directory and files. In theory, this multiplies your throughput.</p>

<p>In practice, it does — until they step on each other.</p>

<h2 id="the-contention-problem">The contention problem</h2>

<p>Fleet agents don’t talk to each other. They don’t know the others exist. When two agents modify the same files, you get collisions: one agent finishes, commits, and moves on. The other agent tries to complete, realizes its working state is gone, resets the branch, and tries to reapply its changes — discarding the first agent’s work because it looks “unrelated.” Sometimes both agents end up in this loop. Nobody wins.</p>

<p>I spent hours building out features across multiple agents. Things were looking great. Then I noticed some features had stopped working. When I asked the agent about it, it casually told me that another background agent had “basically rewritten the repo” — and suggested I wait for the background agent to finish so it could try to reconcile the conflicting changes. There’s no async communication channel between the main session and fleet agents. You can’t steer them, warn them, or update their instructions mid-flight. You just wait.</p>

<p>Contrast this with other agent frameworks — some, like <a href="https://github.com/openclaw/openclaw">OpenClaw</a>, let the orchestrating agent steer background agents in real time: send new constraints, redirect work, or kill a task that’s gone off track. That’s a fundamentally different model. Copilot fleets today are fire-and-forget.</p>

<p>No warning. No conflict detection. No merge. Changes just get lost.</p>

<h2 id="temporal-contention">Temporal contention</h2>

<p>There’s a subtler version of this that’s harder to avoid:</p>

<p><img src="/assets/images/temporal-contention.svg" alt="Temporal contention diagram showing two agents dispatched minutes apart, working on the same files with no coordination channel" /></p>

<p>You have an idea, dispatch an agent. Two minutes later you have a <em>related</em> idea. The first agent is still running — there’s no way to update it. And there’s no easy way for the orchestrator to know a priori whether the new task will conflict with the running one. So it dispatches. Contention.</p>

<p>The only mechanism today is killing the first agent and starting over. There’s no “hey, also do this” channel.</p>

<p>You might think planning helps. And it does — you can spend more time upfront specifying exactly what each agent should do before dispatching. Copilot’s <a href="https://docs.github.com/en/copilot/how-tos/copilot-cli/cli-best-practices#plan-mode">plan mode</a> is great for this. But humans keep thinking after they hit submit. That’s just how brains work. Plan mode reduces contention, it doesn’t eliminate it.</p>

<h2 id="the-formula">The formula</h2>

<p>I think about contention as:</p>

<div style="text-align:center; margin:1.5em 0; padding:1em; background:#1e293b; border-radius:8px; border:1px solid #334155;">
  <span style="font-size:1.3em; font-family:'Georgia',serif; color:#f8fafc; letter-spacing:0.02em;">
    contention &prop;
    <span style="display:inline-block; vertical-align:middle; text-align:center; margin:0 0.15em;">
      <span style="display:block; border-bottom:1px solid #94a3b8; padding:0 0.2em 2px;">∂ideas</span>
      <span style="display:block; padding:2px 0.2em 0;">∂t</span>
    </span>
    &times; agent_execution_time
  </span>
</div>

<p>If you have ideas faster than agents can finish, you’ll have overlapping agents. Speed helps, but even a fast agent doesn’t eliminate the need for fleets. Fleets let you decompose a big problem into parallel tracks. That’s real, not just a speed workaround.</p>

<p>To be clear: Copilot is excellent, and fleets are a genuinely powerful feature. I’m confident they’ll get smarter about coordination over time. This is just my experience in March 2026 — a snapshot of where things are today, not a verdict on where they’re going. The gaps I’m describing are solvable, and GitHub is iterating fast — that team is on fire, honestly one of the most productive teams I’ve seen.</p>

<h2 id="three-rules-i-follow-now">Three rules I follow now</h2>

<p>After losing work a few times, I settled on three rules that have held up:</p>

<h3 id="1-tdd-is-non-negotiable">1. TDD is non-negotiable</h3>

<p>Write tests before code. Every feature, every diagnostic, every behavior gets a test first. Not because <a href="https://en.wikipedia.org/wiki/Test-driven_development">TDD</a> is a religion. Tests are how you detect when one agent broke what another agent built.</p>

<p>When an agent opens a PR and the tests pass, you have confidence. When they fail, you know exactly what regressed. Without tests, you’re flying blind — you won’t even notice the damage until much later.</p>

<p>With 3,000+ tests, I catch conflicts almost immediately. That number isn’t an accident. It’s the direct result of treating tests as the ground truth for the project.</p>

<h3 id="2-every-agent-gets-a-worktree-and-a-branch">2. Every agent gets a worktree and a branch</h3>

<p>No agent works on main. Ever. Each background agent gets its own git worktree on its own branch. This gives you isolation by default. Agents can’t silently overwrite each other’s work because they’re working in different directories on different branches.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git worktree add /tmp/agent-task-123 <span class="nt">-b</span> feature/add-diagnostics
</code></pre></div></div>

<p>This is cheap. Worktrees are lightweight. And it means you always have a clean merge point where you can see exactly what changed.</p>

<p>There’s a file-level version of this trick too. In C#, I started having background agents write new code into separate files using <code class="language-plaintext highlighter-rouge">partial class</code> instead of editing the main class file. Agent A works in <code class="language-plaintext highlighter-rouge">Parser.cs</code>, Agent B writes to <code class="language-plaintext highlighter-rouge">Parser.Diagnostics.cs</code> — same class, different files, zero merge conflicts. The main agent can consolidate later if the split doesn’t make sense, but often it does and you end up with better-organized code anyway.</p>

<p>Same principle as worktrees — don’t touch the same artifact — just applied at a different granularity.</p>

<h3 id="3-one-merge-authority">3. One merge authority</h3>

<p>The main agent (or me) handles all merges. Background agents propose changes via PRs. They don’t merge anything themselves. This creates a single coordination point where conflicts are visible and resolvable.</p>

<p>If two agents touched the same area, I see it at merge time. Not after the damage is done.</p>

<h2 id="enforcing-the-rules">Enforcing the rules</h2>

<p>I store these rules in GitHub Copilot’s native <a href="https://github.blog/ai-and-ml/github-copilot/building-an-agentic-memory-system-for-github-copilot/">repo memory</a>. You just tell Copilot “remember this” and it does — you’ll see it invoke a <code class="language-plaintext highlighter-rouge">store_memory</code> tool call, a built-in tool for persisting conventions across sessions. When an agent spins up, it loads these automatically. The memory tells it: work in a worktree, write tests first, don’t merge.</p>

<p>Not foolproof. Agents can still drift. But the failure mode goes from “silently lost a day of work” to “caught it in a PR review.”</p>

<h2 id="what-i-wish-existed">What I wish existed</h2>

<p>The rules above work if you’re disciplined. But there are gaps that discipline can’t cover:</p>

<p><strong>Agent-to-agent communication.</strong> If Agent A is modifying the parser and Agent B is about to touch the same area, B should know. Even a simple “these files are locked” mechanism would help.</p>

<p><strong>Worktrees as a default.</strong> Today you have to explicitly set this up. It should be the default behavior for any background agent — spin up in an isolated worktree, propose changes via PR, never touch the main branch directly.</p>

<p><strong>Tests as ground truth.</strong> The ecosystem should treat passing tests as the primary integration signal, not just “did the agent say it’s done.” If tests fail after an agent’s changes, that’s a hard stop, not a suggestion.</p>

<h2 id="youre-not-the-only-one-hitting-this">You’re not the only one hitting this</h2>

<p>Niko Heikkilä <a href="https://www.linkedin.com/posts/nikoheikkila_recently-claude-code-shipped-a-batch-skill-share-7441516741974900736-pE3U">tried Claude Code’s /batch for a framework migration</a> — parallel worktrees, PRs per task, e2e tests as gates. He got merge conflicts between parallel PRs, doom loops trying to fix “done” work, and ultimately called the project off. Tests were there but coordination wasn’t.</p>

<p>Tamir Dresher wrote about <a href="https://tamirdresher.github.io/blog/2026/03/22/scaling-ai-part7-enterprise-state">the same problem from the state management angle</a> — squad memory files polluting code PRs when multiple agents share state. Different angle, same underlying issue.</p>

<p>GitHub clearly sees it too — Brady Gaster’s team just shipped <a href="https://github.blog/ai-and-ml/github-copilot/how-squad-runs-coordinated-ai-agents-inside-your-repository/">Squad</a>, a coordinated multi-agent system that drops a specialized team (lead, frontend dev, backend dev, tester) directly into your repo. Squad solves a different layer than what I’m describing here: it handles <em>who does what</em> — role specialization, internal review loops, shared project memory. My rules handle <em>how agents don’t step on each other</em> — worktrees, file isolation, merge authority. They’re complementary. I haven’t tried Squad yet but it’s next on my list.</p>

<h2 id="so-what-now">So what now</h2>

<p>Agent fleets work. I built a real project with them and the throughput is genuinely impressive. But they work like concurrent programming: without synchronization primitives, you get race conditions.</p>

<p>Tests catch the damage. Worktrees prevent the collision. One merge authority gives you a place to reconcile.</p>

<p>Until the tooling catches up, that’s what I’ve got. It’s working.</p>

<p>I’ve distilled these into a <a href="https://gist.github.com/asklar/234e163b316b571ff014e53b0fabd183">SKILL.md</a> you can drop into your repo. Any agent that reads it will follow the rules automatically.</p>

<hr />

<p><em>I write about building with AI agents, the stuff that actually works and the stuff that breaks. <a href="https://www.linkedin.com/in/asklar/">Follow me on LinkedIn</a> for more.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="AI" /><category term="Tools" /><category term="agents" /><category term="copilot" /><category term="fleets" /><category term="workflow" /><category term="git" /><summary type="html"><![CDATA[I used AI agents to build a real compiler — 3,000+ tests, 91 diagnostics, better than what we had. But fleets don't talk to each other. Here's what broke and the rules I follow now.]]></summary></entry><entry><title type="html">MCP Tool Annotations Are Not Security</title><link href="https://asklar.dev/ai/security/2026/03/23/mcp-tool-annotations-not-security.html" rel="alternate" type="text/html" title="MCP Tool Annotations Are Not Security" /><published>2026-03-23T00:00:00-07:00</published><updated>2026-03-23T00:00:00-07:00</updated><id>https://asklar.dev/ai/security/2026/03/23/mcp-tool-annotations-not-security</id><content type="html" xml:base="https://asklar.dev/ai/security/2026/03/23/mcp-tool-annotations-not-security.html"><![CDATA[<p>Last week the MCP project published a <a href="https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/">blog post on tool annotations</a>, recapping the current state: four boolean hints (<code class="language-plaintext highlighter-rouge">readOnlyHint</code>, <code class="language-plaintext highlighter-rouge">destructiveHint</code>, <code class="language-plaintext highlighter-rouge">idempotentHint</code>, <code class="language-plaintext highlighter-rouge">openWorldHint</code>) that servers attach to their tools. The post is honest about what annotations can and can’t do. I want to build on that honesty and say something directly: <strong>tool annotations are not security. They were never designed to be. And stacking more annotations on top won’t change that.</strong></p>

<h2 id="the-spec-says-it-plainly">The spec says it plainly</h2>

<p>The MCP specification is explicit: annotations are not guaranteed to faithfully describe tool behavior. Clients must treat them as untrusted unless they come from a trusted server. During the <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/185#discussion_r2010043988">original proposal review</a>, MCP co-creator Justin Spahr-Summers asked the question that still hangs over every annotation proposal:</p>

<blockquote>
  <p>I think the information itself, if it could be trusted, would be very useful, but I wonder how a client makes use of this flag knowing that it’s not trustable.</p>
</blockquote>

<p>Basil Hosmer <a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/185#discussion_r2010702646">pushed even harder</a>:</p>

<blockquote>
  <p>Clients should ignore annotations from untrusted servers.</p>
</blockquote>

<p>These are the people who built the protocol. They know annotations can’t be trusted, and they shipped them anyway as a UX convenience. That was a reasonable choice. The problem starts when people treat these hints as a security boundary.</p>

<h2 id="five-seps-same-problem">Five SEPs, same problem</h2>

<p>Right now there are five open Specification Enhancement Proposals trying to add more annotations: trust levels, sensitivity markers, secret handling hints, unsafe output flags, governance metadata. Some come from GitHub and OpenAI, based on real gaps they hit running MCP in production.</p>

<p>I get the impulse. You’re building an agent platform, you want to know what a tool does before you run it, and annotations feel like the obvious place to put that information. But adding more metadata to an untrusted channel doesn’t make the channel trusted. A tool that says <code class="language-plaintext highlighter-rouge">secretHint: true</code> is making a promise. Who verifies that promise? The tool itself. That’s circular.</p>

<p>More annotations give you a richer vocabulary for describing risk. That’s useful for building better confirmation dialogs and audit logs. But it’s not enforcement. It’s labeling.</p>

<h2 id="the-lethal-trifecta-needs-a-runtime-answer">The lethal trifecta needs a runtime answer</h2>

<!-- URL not auto-verified: manually confirmed by author -->
<p>Simon Willison named the <a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/">lethal trifecta</a>: an agent with access to private data, exposure to untrusted content, and the ability to exfiltrate. When all three are present, you’re one prompt injection away from data theft.
<!-- URL not auto-verified: manually confirmed by author -->
Researchers have <a href="https://layerxsecurity.com/blog/claude-desktop-extensions-rce/">demonstrated this</a> with a malicious Google Calendar event, an MCP calendar server, and a local code execution tool.</p>

<p>The MCP blog post acknowledges this. But then it frames annotations as part of the solution, helping clients reason about which tools contribute to which legs of the trifecta. And sure, if <code class="language-plaintext highlighter-rouge">openWorldHint</code> is set correctly, a client could theoretically refuse to combine that tool with one that reads private data.</p>

<p>Here’s the problem: <code class="language-plaintext highlighter-rouge">openWorldHint</code> is self-reported. A malicious server won’t set it. A lazy server author won’t think about it. A compromised server might have set it correctly last week but not today. You can’t build a security model on “the tool describes itself honestly.” That’s asking a burglar to wear a name tag.</p>

<p>The lethal trifecta has to be broken at the runtime level: the container, the sandbox, the execution environment. If a tool can’t reach the network, it doesn’t matter what <code class="language-plaintext highlighter-rouge">openWorldHint</code> says. If a tool can only read files in <code class="language-plaintext highlighter-rouge">/tmp/workspace</code>, it can’t exfiltrate your emails regardless of how it describes itself. The enforcement has to be about what the tool <strong>can do</strong>, not what it <strong>says</strong> it does.</p>

<h2 id="annotations-are-ux-not-policy">Annotations are UX, not policy</h2>

<p>I’m not saying annotations are useless. They’re good for UX. If a tool says it’s destructive, showing a confirmation dialog before running it is smart. If a tool says it’s read-only, maybe you skip the confirmation. That’s a better user experience.</p>

<p>But UX is not policy. A confirmation dialog doesn’t prevent exfiltration. It just makes the user click “OK” first. In an agentic workflow where the LLM makes tool calls autonomously, there might not even be a user in the loop to click anything.</p>

<p>The distinction matters because I see people conflating the two. “We have tool annotations, so we have a security story.” No. You have a hint system. Your security story is whatever runs underneath: process isolation, network policies, filesystem restrictions, capability-based permissions. Without those, annotations are a sign on the door that says “please don’t steal anything.”</p>

<h2 id="what-enforcement-actually-looks-like">What enforcement actually looks like</h2>

<p>Real enforcement means the runtime environment constrains what a tool can do, independent of what the tool claims about itself:</p>

<ul>
  <li><strong>Network isolation.</strong> A tool that processes documents doesn’t need outbound HTTP. Don’t give it outbound HTTP.</li>
  <li><strong>Filesystem scoping.</strong> Mount only what the tool needs. If it’s a code formatter, it gets the source directory. Not your home folder.</li>
  <li><strong>Capability dropping.</strong> No raw socket access. No process spawning unless explicitly needed.</li>
  <li><strong>Ephemeral environments.</strong> Spin up a container for the tool invocation. Tear it down after. State doesn’t persist unless you explicitly allow it.</li>
</ul>

<p>None of this requires the tool’s cooperation. None of it depends on annotations being accurate. The tool doesn’t get to vote on its own permissions.</p>

<p>This is the direction the industry is converging on. The protocol layer is the wrong place to solve trust. The runtime layer is where you actually have leverage.</p>

<h2 id="the-meta-question">The meta-question</h2>

<p>Every time a new annotation SEP shows up, I ask the same question: <strong>who enforces this?</strong></p>

<p>If the answer is “the client reads the annotation and decides what to do,” that’s UX. If the answer is “the runtime environment constrains the tool regardless of annotations,” that’s security. The MCP community is pouring energy into the first category while the second category still has no standardized answer.</p>

<p>I’d love to see that energy redirected. Give me a standard way to declare what capabilities a tool needs. Give me a runtime spec that hosts can implement. Give me a container profile format that tool authors ship alongside their servers. That would be worth five SEPs.</p>

<p>Annotations tell you what a tool says about itself. Sandboxes tell you what a tool can actually do. I know which one I’m betting on.</p>

<hr />

<p><em>I write about building with AI agents, the stuff that actually works and the stuff that breaks. <a href="https://www.linkedin.com/in/asklar/">Follow me on LinkedIn</a> for more.</em></p>]]></content><author><name>Alexander Sklar</name></author><category term="ai" /><category term="security" /><category term="mcp" /><category term="agents" /><category term="security" /><category term="tool-annotations" /><summary type="html"><![CDATA[MCP tool annotations are self-reported hints from untrusted servers. That's useful for UX. It's not a security model.]]></summary></entry><entry><title type="html">Agentic Building Blocks: Instructions vs Prompt Files vs Custom Agents vs Skills vs MCP vs Hooks</title><link href="https://asklar.dev/ai/tools/2026/03/18/agentic-artifacts.html" rel="alternate" type="text/html" title="Agentic Building Blocks: Instructions vs Prompt Files vs Custom Agents vs Skills vs MCP vs Hooks" /><published>2026-03-18T00:00:00-07:00</published><updated>2026-03-18T00:00:00-07:00</updated><id>https://asklar.dev/ai/tools/2026/03/18/agentic-artifacts</id><content type="html" xml:base="https://asklar.dev/ai/tools/2026/03/18/agentic-artifacts.html"><![CDATA[<p>If you’re building with agents today, you’re going to run into a pile of “things” that all sound similar:</p>

<ul>
  <li><a href="#instructions">Instructions</a></li>
  <li><a href="#prompt-files">Prompt files</a></li>
  <li><a href="#custom-agents">Custom agents / modes</a></li>
  <li><a href="#skills">Skills</a></li>
  <li><a href="#plugins">Plugins</a></li>
  <li><a href="#mcp">MCP servers</a></li>
  <li><a href="#hooks">Hooks</a></li>
</ul>

<p>They’re not redundant. They exist because they solve <strong>different layers of control</strong>.</p>

<p>This post explains what each one is, what it’s good for, and a decision guide for “which should I use?” — written so you can hand it to someone with zero context.</p>

<hr />

<h2 id="the-core-axes-scope-trigger-enforcement-and-distribution">The core axes: scope, trigger, enforcement (and distribution)</h2>

<p>When you’re deciding between these primitives, ask four questions:</p>

<p>1) <strong>Scope</strong> — where does it apply?</p>
<ul>
  <li>one repo</li>
  <li>one user</li>
  <li>one session</li>
  <li>an entire org</li>
</ul>

<p>2) <strong>Trigger</strong> — when does it run?</p>
<ul>
  <li>always-on</li>
  <li>explicitly invoked</li>
  <li>automatically discovered</li>
  <li>event-driven</li>
</ul>

<p>3) <strong>Enforcement</strong> — how deterministic is it?</p>
<ul>
  <li>guidance (model can ignore)</li>
  <li>packaging (reusable but still model-driven)</li>
  <li>gates (hard allow/deny)</li>
</ul>

<p>4) <strong>Distribution</strong> — how does it propagate?</p>
<ul>
  <li><strong>Personal install</strong> (user-level): installed once, used across repos you touch</li>
  <li><strong>Repo install</strong> (checked in): travels with the codebase; everyone gets it on clone</li>
</ul>

<p>If you pick by these axes, “which thing do I build?” becomes straightforward.</p>

<hr />

<h2 id="a-simple-map-how-these-compose">A simple map (how these compose)</h2>

<figure style="margin: 2.2rem 0;">
  <a href="/assets/images/posts/agentic-artifacts/agentic-building-blocks-overview.png" target="_blank" rel="noopener" style="display:block;">
    <img src="/assets/images/posts/agentic-artifacts/agentic-building-blocks-overview.png" alt="Agentic building blocks overview" style="display:block; width:100%; height:auto; border-radius:16px; box-shadow: 0 12px 40px rgba(0,0,0,0.35);" />
  </a>
  <figcaption style="margin-top: 0.6rem; font-size: 0.95rem; opacity: 0.8;">
    A lightweight mental model: preferences, macros, roles, recipes, connectors, gates.
  </figcaption>
</figure>

<p>Think of it like a stack:</p>
<ul>
  <li><strong>Instructions/prompt files</strong> shape behavior.</li>
  <li><strong>Custom agents</strong> define “who is the agent right now” and what tools it’s allowed to use.</li>
  <li><strong>Skills</strong> package repeatable workflows + assets.</li>
  <li><strong>MCP</strong> is an <em>optional connector layer</em> that exposes tools/resources to the agent (often external systems, sometimes local tools too).</li>
  <li><strong>Hooks</strong> enforce non-negotiables.</li>
</ul>

<hr />

<p><a id="instructions"></a></p>
<h2 id="1-instructions-always-on-defaults">1) Instructions (always-on defaults)</h2>

<p><strong>Docs (GitHub Copilot / CLI):</strong> <a href="https://docs.github.com/en/copilot/concepts/prompting/response-customization">Response customization</a></p>

<p><strong>What it is:</strong> a set of rules that are automatically included in context.</p>

<p><strong>Important nuance:</strong> instructions typically <em>stack</em> from multiple scopes:</p>
<ul>
  <li><strong>Repo-wide instructions</strong> (checked in; team defaults)</li>
  <li><strong>Path-specific instructions</strong> (only apply to certain files/globs; keeps rules local)</li>
  <li><strong>Personal instructions</strong> (your preferences, not the team’s)</li>
</ul>

<p>If your agent feels “mysteriously inconsistent”, a good debugging move is to <strong>inspect which instruction files were loaded</strong> for the session (many hosts expose a <code class="language-plaintext highlighter-rouge">/instructions</code>-style command).</p>

<p><strong>Use it when:</strong> you want the agent to behave consistently without anyone remembering to invoke anything.</p>

<p><strong>Great for:</strong></p>
<ul>
  <li>coding conventions, naming, lint expectations</li>
  <li>security defaults (least privilege, no secrets in code)</li>
  <li>process defaults (“always show diff before asking me to merge”)</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>you need a named workflow people can invoke</li>
  <li>you need deterministic enforcement (use hooks)</li>
</ul>

<p><strong>Rule of thumb:</strong> instructions should be short and stable. If they change weekly, they’re a prompt or a skill.</p>

<hr />

<p><a id="prompt-files"></a></p>
<h2 id="2-prompt-files-named-plays">2) Prompt files (named plays)</h2>

<p><strong>Docs (GitHub Copilot / CLI):</strong> <a href="https://docs.github.com/en/copilot/concepts/prompting/response-customization?tool=vscode#about-prompt-files">Prompt files (VS Code)</a></p>

<p><strong>What it is:</strong> an explicitly invoked prompt template (often a slash command).</p>

<p><strong>Use it when:</strong> you keep typing the same thing over and over.</p>

<p><strong>Great for:</strong></p>
<ul>
  <li>“write release notes from these commits”</li>
  <li>“summarize this PR as a changelog entry”</li>
  <li>“generate an RFC from these bullets”</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>you need bundled files/scripts/templates (use a skill)</li>
  <li>you want always-on standards (instructions)</li>
</ul>

<p><strong>Rule of thumb:</strong> prompt files are the “macros” of agent work.</p>

<hr />

<p><a id="custom-agents"></a></p>
<h2 id="3-custom-agents--modes-role--tool-boundary">3) Custom agents / modes (role + tool boundary)</h2>

<p><strong>Docs (GitHub Copilot / CLI):</strong> <a href="https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-custom-agents">Custom agents</a></p>

<p><strong>What it is:</strong> a pre-defined agent configuration you select for a session: persona + allowed tools + guardrails.</p>

<p>This is the sharpest tool for preventing expensive mistakes.</p>

<p><strong>Use it when:</strong> you need different behavior/tooling for different phases:</p>
<ul>
  <li>planning vs implementation</li>
  <li>review vs edit</li>
  <li>security/audit vs coding</li>
</ul>

<p><strong>Great for:</strong></p>
<ul>
  <li>a <strong>Planner</strong> that can read but not edit</li>
  <li>a <strong>Reviewer</strong> that can run checks but not push changes</li>
  <li>an <strong>Implementer</strong> that can edit files and run tests</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>you just need a one-off task template (prompt)</li>
  <li>you need a reusable multi-step workflow with assets (skill)</li>
</ul>

<p><strong>Rule of thumb:</strong> modes are “roles on a team”. If you’d separate the work between two humans, split it into two modes.</p>

<hr />

<p><a id="skills"></a></p>
<h2 id="4-skills-packaged-workflows">4) Skills (packaged workflows)</h2>

<p><strong>Docs (GitHub Copilot / CLI):</strong> <a href="https://docs.github.com/en/copilot/concepts/agents/about-agent-skills">Agent skills</a></p>

<p><strong>What it is:</strong> a folder-packaged runbook: the instructions plus whatever assets the workflow needs (templates, scripts, reference files).</p>

<p>Skills are “recipes”: <strong>multi-step workflows</strong> that get pulled in when relevant, rather than living as always-on background context.</p>

<p><strong>Use it when:</strong> the workflow is multi-step and you want it to be repeatable.</p>

<p><strong>Great for:</strong></p>
<ul>
  <li>incident triage playbooks</li>
  <li>postmortem generation (with templates)</li>
  <li>project bootstrap scaffolding</li>
  <li>“evaluation loops” (feedback → analysis → proposed change)</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>it’s just a standard rule (instructions)</li>
  <li>it’s just a one-shot template (prompt)</li>
  <li>you’re actually trying to connect to a live system (MCP)</li>
</ul>

<p><strong>Rule of thumb:</strong> if you’re attaching files, scripts, templates, or examples, you’re in skill territory.</p>

<hr />

<p><a id="plugins"></a></p>
<h2 id="5-plugins-distribution-package-for-a-host">5) Plugins (distribution package for a host)</h2>

<p><strong>Docs:</strong> (varies by host; “plugin” is not a single universal standard in Copilot docs)</p>

<p><strong>What it is:</strong> an installable unit for a specific host ecosystem (Copilot, VS Code, internal CLI, etc.).</p>

<p>A plugin can contain many things: skills, prompt files, agents, scripts, metadata.</p>

<p><strong>Use it when:</strong> adoption and lifecycle matter:</p>
<ul>
  <li>install/update/uninstall</li>
  <li>discovery (catalog/marketplace)</li>
  <li>compatibility/versioning</li>
</ul>

<p><strong>Great for:</strong></p>
<ul>
  <li>packaging capabilities so teams can install them a la carte</li>
  <li>sharing a consistent bundle without copying files repo-to-repo</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>you’re still exploring and changing the workflow daily (skills/prompts iterate faster)</li>
  <li>you need cross-host portability (plugins are usually host-coupled)</li>
</ul>

<p><strong>Rule of thumb:</strong> skills are <em>content</em>. plugins are <em>shipping</em>.</p>

<hr />

<p><a id="mcp"></a></p>
<h2 id="6-mcp-servers-toolsresources-connector-layer">6) MCP servers (tools/resources connector layer)</h2>

<p><strong>Docs (GitHub Copilot / CLI):</strong> <a href="https://docs.github.com/en/copilot/concepts/context/mcp">MCP</a></p>

<p><strong>What it is:</strong> a standard protocol for exposing <strong>tools</strong> and <strong>resources</strong> to an agent.</p>

<p>Often, those tools/resources front a live external system (GitHub, tickets, cloud, databases). But MCP can also be used to wrap <strong>local</strong> capabilities (for example, a locally running service that brokers auth, or a tool that’s easier to expose over a protocol).</p>

<p><strong>Use it when:</strong> the agent needs capabilities or data that aren’t already in its local context, especially when you want a stable interface:</p>
<ul>
  <li>GitHub/AzDO operations</li>
  <li>ticketing systems</li>
  <li>cloud control planes</li>
  <li>databases / internal services</li>
</ul>

<p><strong>Great for:</strong></p>
<ul>
  <li>“create an issue / comment on a PR / fetch build logs”</li>
  <li>“query a service dashboard and summarize anomalies”</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>all you need is static guidance or a local workflow</li>
  <li>you can accomplish the same thing with a CLI without running a server</li>
</ul>

<p><strong>Rule of thumb:</strong> MCP is about <strong>interfaces and connectivity</strong>. If you’re not exposing tools/resources across a boundary (process, machine, auth domain), MCP is usually overkill.</p>

<hr />

<p><a id="hooks"></a></p>
<h2 id="7-hooks-deterministic-policy-gates">7) Hooks (deterministic policy gates)</h2>

<p><strong>Docs:</strong> <a href="https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks">Hooks</a></p>

<p><strong>What it is:</strong> event-driven checks that can allow/deny an operation at execution time.</p>

<p><strong>Use it when:</strong> “the model should always…” is not good enough.</p>

<p><strong>Great for:</strong></p>
<ul>
  <li>blocking dangerous commands</li>
  <li>enforcing compliance checks</li>
  <li>audit logging</li>
</ul>

<p><strong>Don’t use it when:</strong></p>
<ul>
  <li>the rule is subjective or fuzzy</li>
</ul>

<p><strong>Rule of thumb:</strong> if violating the rule is catastrophic, make it a hook.</p>

<hr />

<h2 id="distribution-cheat-sheet-personal-vs-repo">Distribution cheat sheet (personal vs repo)</h2>

<p>Different tools name this differently, but the pattern is consistent:</p>

<ul>
  <li><strong>Personal</strong>: great for rapid iteration and power users; teammates won’t see it unless you document it.</li>
  <li><strong>Repo</strong>: great for onboarding and consistency; versioned and reviewed like code.</li>
</ul>

<p>Here’s where these artifacts most commonly live:</p>

<ul>
  <li><strong>Instructions</strong>: repo (and sometimes org-managed)</li>
  <li><strong>Prompt files</strong>: repo</li>
  <li><strong>Custom agents / modes</strong>: repo (sometimes personal)</li>
  <li><strong>Skills</strong>: both (personal skill library <em>or</em> repo-scoped skills)</li>
  <li><strong>Plugins</strong>: personal install via marketplace/registry (the plugin itself may <em>deliver</em> repo-scoped content)</li>
  <li><strong>MCP servers</strong>: both (workspace config vs user config)</li>
  <li><strong>Hooks</strong>: repo/org (because enforcement should be shared)</li>
</ul>

<hr />

<h2 id="chooser-matrix-pick-the-smallest-thing-that-works">Chooser matrix (pick the smallest thing that works)</h2>

<figure style="margin: 2.2rem 0;">
  <a href="/assets/images/posts/agentic-artifacts/agentic-building-blocks-chooser-matrix.png" target="_blank" rel="noopener" style="display:block;">
    <img src="/assets/images/posts/agentic-artifacts/agentic-building-blocks-chooser-matrix.png" alt="Agentic building blocks chooser matrix" style="display:block; width:100%; height:auto; border-radius:16px; box-shadow: 0 12px 40px rgba(0,0,0,0.35);" />
  </a>
  <figcaption style="margin-top: 0.6rem; font-size: 0.95rem; opacity: 0.8;">
    Pick by scope, trigger, enforcement, and interface boundaries.
  </figcaption>
</figure>

<p>One nuance: <strong>plugins</strong> are orthogonal. If you need distribution + install/update + catalog UX, wrap whatever you built (prompts/skills/agents) inside a plugin.</p>

<hr />

<h2 id="what-good-looks-like-a-minimal-stack">What “good” looks like (a minimal stack)</h2>

<p>Most teams don’t need all of this.</p>

<p>A sane starting point:
1) <strong>Instructions</strong> for baseline rules
2) <strong>3–5 prompt files</strong> for your most common plays
3) <strong>Two modes</strong> (Planner + Implementer) if you’re doing real code changes
4) <strong>1–2 skills</strong> for the workflows you want repeatable
5) <strong>MCP only</strong> where you need tool/resource connectors
6) <strong>Hooks</strong> only for non-negotiables</p>

<p>The goal is not to build an “AI config cathedral.”</p>

<p>The goal is to pick the smallest building block that makes your agent work more reliable, more reusable, or more adoptable.</p>

<hr />

<h2 id="final-note-its-all-just-text-and-thats-a-feature">Final note: it’s all just text (and that’s a feature)</h2>

<p>At the end of the day, most of these “building blocks” are files containing text: markdown instructions, prompt templates, and lightweight configuration.</p>

<p>What makes them powerful isn’t the file format. It’s the <strong>conventions</strong>:</p>
<ul>
  <li>common names (instructions, prompt files, custom agents, skills)</li>
  <li>common locations (<code class="language-plaintext highlighter-rouge">.github/...</code>, <code class="language-plaintext highlighter-rouge">~/.copilot/...</code>)</li>
  <li>common semantics (what gets loaded automatically vs manually)</li>
</ul>

<p>Those conventions are converging across tools.</p>

<p>A concrete example: <strong>Agent Skills</strong> is an open, lightweight format centered around a folder with a <code class="language-plaintext highlighter-rouge">SKILL.md</code> file (YAML frontmatter + markdown instructions), designed for progressive disclosure (discover → activate → execute).</p>

<ul>
  <li><a href="https://agentskills.io/what-are-skills.md">What are skills?</a></li>
  <li><a href="https://agentskills.io/specification">Agent Skills specification</a></li>
</ul>

<p>That’s the real story: we’re starting to get a shared vocabulary for packaging agent behavior.</p>

<hr />

<p>If this was useful, <strong><a href="https://www.linkedin.com/in/asklar/">connect with me on LinkedIn</a></strong>.</p>]]></content><author><name>Alexander Sklar</name></author><category term="AI" /><category term="Tools" /><category term="agents" /><category term="copilot" /><category term="mcp" /><category term="skills" /><category term="plugins" /><category term="workflow" /><summary type="html"><![CDATA[A practical guide to the building blocks of modern agent systems — what each is for, where it fits, and how to choose without cargo-culting.]]></summary></entry></feed>