<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:georss="http://www.georss.org/georss" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:media="http://search.yahoo.com/mrss/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>El Bruno</title>
	<atom:link href="https://elbruno.com/feed/" rel="self" type="application/rss+xml"/>
	<link>https://elbruno.com</link>
	<description>Dev Advocating &#129361; @Microsoft</description>
	<lastBuildDate>Fri, 05 Jun 2026 19:43:12 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<site xmlns="com-wordpress:feed-additions:1">19872796</site><cloud domain="elbruno.com" path="/?rsscloud=notify" port="80" protocol="http-post" registerProcedure=""/>
<image>
		<url>https://s0.wp.com/i/webclip.png</url>
		<title>El Bruno</title>
		<link>https://elbruno.com</link>
	</image>
	<atom:link href="https://elbruno.com/osd.xml" rel="search" title="El Bruno" type="application/opensearchdescription+xml"/>
	<atom:link href="https://elbruno.com/?pushpress=hub" rel="hub"/>
	<item>
		<title>Local-first AI Agents in C#: Foundry Local, MEAI, and Microsoft Agent Framework</title>
		<link>https://elbruno.com/2026/06/05/local-first-ai-agents-in-c-foundry-local-meai-and-microsoft-agent-framework/</link>
					<comments>https://elbruno.com/2026/06/05/local-first-ai-agents-in-c-foundry-local-meai-and-microsoft-agent-framework/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Fri, 05 Jun 2026 19:43:12 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Foundry Local]]></category>
		<category><![CDATA[MAF]]></category>
		<category><![CDATA[MEAI]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40751</guid>

					<description><![CDATA[Hi! If you are building local AI apps in C#, you quickly hit a practical gap: That is exactly why I created this library Why I created this I wanted a&#160;clean, non-REST, in-process&#160;integration where: So the library provides a thin adapter: Foundry Local SDK&#160;-&#62;&#160;FoundryLocalChatClientAdapter&#160;-&#62;&#160;IChatClient This lets you write provider-agnostic app code while still running local [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">If you are building local AI apps in C#, you quickly hit a practical gap:</p>



<ul class="wp-block-list">
<li><strong>Foundry Local SDK</strong> is great for local/on-device inference.</li>



<li><strong>Microsoft.Extensions.AI</strong> and <strong>Microsoft Agent Framework</strong> expect an <code>IChatClient</code>.</li>



<li>There is no first-party Foundry Local -&gt; <code>IChatClient</code> bridge yet.</li>
</ul>



<p class="wp-block-paragraph">That is exactly why I created this library</p>



<ul class="wp-block-list">
<li>Nuget: <a href="https://www.nuget.org/packages/ElBruno.MAF.FoundryLocal.Adapter">https://www.nuget.org/packages/ElBruno.MAF.FoundryLocal.Adapter</a></li>



<li>Repo: <a href="https://github.com/elbruno/ElBruno.MAF.FoundryLocal/">https://github.com/elbruno/ElBruno.MAF.FoundryLocal/</a></li>



<li>Sample: <a href="https://github.com/elbruno/ElBruno.MAF.FoundryLocal/tree/main/src/ElBruno.MAF.FoundryLocal.Console">ElBruno.MAF.FoundryLocal/src/ElBruno.MAF.FoundryLocal.Console at main · elbruno/ElBruno.MAF.FoundryLocal</a></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Why I created this<a href="https://github.com/elbruno/ElBruno.MAF.FoundryLocal/blob/main/docs/blog-post-using-foundry-local-wrapper.md#why-i-created-this"></a></h2>



<p class="wp-block-paragraph">I wanted a&nbsp;<strong>clean, non-REST, in-process</strong>&nbsp;integration where:</p>



<ol class="wp-block-list">
<li>Foundry Local stays local and private.</li>



<li>My app code uses standard MEAI abstractions (<code>IChatClient</code>).</li>



<li>The same <code>IChatClient</code> can be reused by Microsoft Agent Framework.</li>
</ol>



<p class="wp-block-paragraph">So the library provides a thin adapter:</p>



<p class="wp-block-paragraph"><code>Foundry Local SDK</code>&nbsp;-&gt;&nbsp;<code>FoundryLocalChatClientAdapter</code>&nbsp;-&gt;&nbsp;<code>IChatClient</code></p>



<p class="wp-block-paragraph">This lets you write provider-agnostic app code while still running local inference.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Minimal usage</h2>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.MAF.FoundryLocal/blob/main/docs/blog-post-using-foundry-local-wrapper.md#minimal-usage"></a></p>



<p class="wp-block-paragraph">Install:</p>



<pre class="wp-block-preformatted"><strong>dotnet add package ElBruno.MAF.FoundryLocal.Adapter<br />dotnet add package Microsoft.Extensions.Hosting<br />dotnet add package Microsoft.Agents.AI</strong></pre>



<p class="wp-block-paragraph">Use the wrapper as&nbsp;<code>IChatClient</code>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.MAF.FoundryLocal;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.Configure&lt;FoundryLocalOptions&gt;(o =&gt;
{
    o.ModelAlias = &quot;qwen2.5-0.5b&quot;;
    o.DownloadIfMissing = true;
    o.UnloadOnExit = true;
});

builder.Services.Configure&lt;ChatRuntimeOptions&gt;(_ =&gt; { });
builder.Services.AddSingleton&lt;FoundryLocalModelLifecycleService&gt;();
builder.Services.AddSingleton&lt;IChatClient, FoundryLocalChatClientAdapter&gt;();

using var host = builder.Build();
var chatClient = host.Services.GetRequiredService&lt;IChatClient&gt;();

var response = await chatClient.GetResponseAsync(
&#91;
    new(ChatRole.User, &quot;Explain local-first AI in one paragraph.&quot;)
]);

Console.WriteLine(response.Text);
</pre></div>


<pre class="wp-block-preformatted"></pre>



<p class="wp-block-paragraph">Reuse the same wrapper with Microsoft Agent Framework:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using Microsoft.Agents.AI;

var agent = new ChatClientAgent(
    chatClient: chatClient,
    instructions: "You are a concise local assistant.",
    name: "LocalFoundryAgent",
    description: "Foundry Local + IChatClient adapter",
    tools: null,
    loggerFactory: null,
    services: null);

var agentResponse = await agent.RunAsync(
    "Give me 3 bullet points about local AI in .NET.",
    session: null,
    options: null);

Console.WriteLine(agentResponse);
</pre></div>


<pre class="wp-block-preformatted"></pre>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What this gives you<a href="https://github.com/elbruno/ElBruno.MAF.FoundryLocal/blob/main/docs/blog-post-using-foundry-local-wrapper.md#what-this-gives-you"></a></h2>



<ul class="wp-block-list">
<li>Local inference through Foundry Local SDK</li>



<li>No local REST server required</li>



<li>Standard MEAI <code>IChatClient</code> programming model</li>



<li>Easy path to Agent Framework agents</li>
</ul>



<p class="wp-block-paragraph">In short:&nbsp;<strong>local model runtime + standard .NET AI abstractions + minimal glue code</strong>.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/06/05/local-first-ai-agents-in-c-foundry-local-meai-and-microsoft-agent-framework/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40751</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>GitHub Copilot and tokens: how to keep using AI without burning your budget in three prompts (some personal lessons learned!)</title>
		<link>https://elbruno.com/2026/06/04/github-copilot-and-tokens-how-to-keep-using-ai-without-burning-your-budget-in-three-prompts-some-personal-lessons-learned/</link>
					<comments>https://elbruno.com/2026/06/04/github-copilot-and-tokens-how-to-keep-using-ai-without-burning-your-budget-in-three-prompts-some-personal-lessons-learned/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 19:18:53 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub Copilot]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40745</guid>

					<description><![CDATA[Hi! For a long time, many of us used GitHub Copilot as if it were unlimited magic: autocomplete, chat, agent mode, code review, increasingly powerful models, massive context, and long-running sessions that sometimes felt like a pair-programming marathon. And it worked. Well, mostly. Now, with usage-based billing and AI Credits, many developers are seeing something [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img width="836" height="471" src="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png" alt="" class="wp-image-40749" srcset="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png 836w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png?w=150&amp;h=85 150w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png?w=300&amp;h=169 300w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png?w=768&amp;h=433 768w" sizes="(max-width: 836px) 100vw, 836px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">For a long time, many of us used GitHub Copilot as if it were unlimited magic: autocomplete, chat, agent mode, code review, increasingly powerful models, massive context, and long-running sessions that sometimes felt like a pair-programming marathon.</p>



<p class="wp-block-paragraph">And it worked. Well, mostly.</p>



<p class="wp-block-paragraph">Now, with usage-based billing and AI Credits, many developers are seeing something that used to be mostly invisible: every AI interaction has a cost. And that cost is not only about “asking a question.” It depends on the model, the context, input tokens, output tokens, cached tokens, tools, files, logs, MCP servers, and how long we let an agent keep working.</p>



<p class="wp-block-paragraph">GitHub explains this in the Copilot billing documentation: interactions consume input, output, and cached tokens; each model has its own pricing; and the total is converted into AI Credits. The same documentation also explains an important detail: code completions and Next Edit Suggestions are not charged as AI Credits in paid plans.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing">https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing</a></p>



<p class="wp-block-paragraph">The natural reaction is to panic.</p>



<p class="wp-block-paragraph">The useful reaction is to optimize.</p>



<p class="wp-block-paragraph">Just like we optimize compute, storage, bandwidth, or GitHub Actions minutes, now we also need to optimize how we use tokens.</p>



<p class="wp-block-paragraph">And yes, this applies to those of us using Copilot every day for .NET, AI, Azure, scripts, demos, documentation, refactors, and those beautiful moments when we tell the agent “just fix this test” and come back 20 minutes later to find a doctoral thesis in progress.</p>



<h2 class="wp-block-heading">The real problem is not the prompt. It is the context</h2>



<p class="wp-block-paragraph">When we talk about tokens, we often think only about the text we type.</p>



<p class="wp-block-paragraph">But in AI-assisted development tools, the expensive part is often everything that travels around the prompt:</p>



<ul class="wp-block-list">
<li>chat history</li>



<li>open files</li>



<li>files attached as context</li>



<li>workspace search results</li>



<li>diffs</li>



<li>terminal output</li>



<li>build errors</li>



<li>long logs</li>



<li>tool calls</li>



<li>MCP server responses</li>



<li>custom instructions</li>



<li>agent memory</li>



<li>content the model decides to inspect while completing the task</li>
</ul>



<p class="wp-block-paragraph">A one-line question can be cheap.</p>



<p class="wp-block-paragraph">A one-line question inside a conversation with 80 messages, 12 files, 3 logs, 5 tools, and an MCP server connected to half the universe… not so much.</p>



<p class="wp-block-paragraph">The first optimization is mental:&nbsp;<strong>more context does not always mean a better answer</strong>.</p>



<p class="wp-block-paragraph">Sometimes more context only means more tokens, more noise, and more chances for the model to get distracted.</p>



<h2 class="wp-block-heading">1. Use autocomplete and Next Edit Suggestions before opening chat</h2>



<p class="wp-block-paragraph">Not everything needs a conversation.</p>



<p class="wp-block-paragraph">For small tasks, Copilot directly in the editor is often the most efficient option:</p>



<ul class="wp-block-list">
<li>completing a line</li>



<li>finishing a simple function</li>



<li>generating boilerplate</li>



<li>suggesting the next obvious change</li>



<li>completing a repeated pattern</li>



<li>adjusting names</li>



<li>writing a simple condition</li>



<li>generating a property, DTO, or mapping</li>
</ul>



<p class="wp-block-paragraph">If you can solve it with&nbsp;<code>Tab</code>, do not open a chat.</p>



<p class="wp-block-paragraph">This is not just convenience. It is strategy. According to GitHub documentation, code completions and Next Edit Suggestions are not billed as AI Credits in paid plans.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing">https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing</a></p>



<p class="wp-block-paragraph">Simple rule:</p>



<ul class="wp-block-list">
<li>Use autocomplete for micro-tasks.</li>



<li>Use inline edit for local changes.</li>



<li>Use chat for questions that require reasoning.</li>



<li>Use agent mode for well-scoped multi-file tasks.</li>



<li>Use cloud agents when you really want to delegate a workflow, not when you only need to change three lines.</li>
</ul>



<p class="wp-block-paragraph">The most expensive model in the world should not be helping you write&nbsp;<code>public string Name { get; set; }</code>.</p>



<p class="wp-block-paragraph">That is what&nbsp;<code>Tab</code>&nbsp;is for. And coffee.</p>



<h2 class="wp-block-heading">2. Choose the right model for the task</h2>



<p class="wp-block-paragraph">Not every model has the same cost or the same purpose.</p>



<p class="wp-block-paragraph">The VS Code documentation recommends using lighter models for quick edits, boilerplate, and direct questions, and reserving reasoning models for complex refactors, architecture decisions, and multi-step debugging.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://code.visualstudio.com/docs/copilot/guides/optimize-usage">https://code.visualstudio.com/docs/copilot/guides/optimize-usage</a></p>



<p class="wp-block-paragraph">A practical pattern:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Task type</th><th class="has-text-align-left" data-align="left">Recommended model</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left">Simple question</td><td class="has-text-align-left" data-align="left">Lightweight model or Auto</td></tr><tr><td class="has-text-align-left" data-align="left">Boilerplate</td><td class="has-text-align-left" data-align="left">Lightweight model</td></tr><tr><td class="has-text-align-left" data-align="left">Code explanation</td><td class="has-text-align-left" data-align="left">Lightweight or medium model</td></tr><tr><td class="has-text-align-left" data-align="left">Simple tests</td><td class="has-text-align-left" data-align="left">Lightweight or medium model</td></tr><tr><td class="has-text-align-left" data-align="left">Complex debugging</td><td class="has-text-align-left" data-align="left">Reasoning model</td></tr><tr><td class="has-text-align-left" data-align="left">Architecture</td><td class="has-text-align-left" data-align="left">Reasoning or frontier model</td></tr><tr><td class="has-text-align-left" data-align="left">Large refactor</td><td class="has-text-align-left" data-align="left">Powerful model, but with limited scope</td></tr><tr><td class="has-text-align-left" data-align="left">Initial documentation</td><td class="has-text-align-left" data-align="left">Lightweight or local model</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">GitHub also documents Auto Model Selection, which can choose a model based on task complexity, availability, and policies. The documentation also notes that Auto can improve efficiency by reserving more expensive models for tasks that actually need them.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://docs.github.com/en/copilot/concepts/auto-model-selection">https://docs.github.com/en/copilot/concepts/auto-model-selection</a></p>



<p class="wp-block-paragraph">My recommendation for most developers:</p>



<ul class="wp-block-list">
<li>use&nbsp;<strong>Auto</strong>&nbsp;as the default</li>



<li>manually switch to a more powerful model only when you have a clear reason</li>



<li>switch back to Auto or a cheaper model when the complex task is done</li>
</ul>



<p class="wp-block-paragraph">Do not drive a truck to buy bread.</p>



<p class="wp-block-paragraph">And do not use the most expensive model to ask how to center a&nbsp;<code>div</code>. Although, to be fair, sometimes centering a&nbsp;<code>div</code>&nbsp;does deserve an architecture review.</p>



<h2 class="wp-block-heading">3. Start new chats when you change tasks</h2>



<p class="wp-block-paragraph">This is one of the simplest and most ignored optimizations.</p>



<p class="wp-block-paragraph">The VS Code documentation is clear: when a conversation grows, it accumulates context from previous messages, tool outputs, and file contents. If you switch to an unrelated task inside the same session, the model still processes irrelevant history.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://code.visualstudio.com/docs/copilot/guides/optimize-usage">https://code.visualstudio.com/docs/copilot/guides/optimize-usage</a></p>



<p class="wp-block-paragraph">Bad pattern:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Chat 1:</div><div class="cm-line">- Debug tests</div><div class="cm-line">- Then architecture</div><div class="cm-line">- Then generate README</div><div class="cm-line">- Then review Dockerfile</div><div class="cm-line">- Then explain an Azure error</div><div class="cm-line">- Then ask for a tweet</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Better pattern:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Chat 1: Debug tests</div><div class="cm-line">Chat 2: Architecture design</div><div class="cm-line">Chat 3: README</div><div class="cm-line">Chat 4: Dockerfile</div><div class="cm-line">Chat 5: Azure deployment issue</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">New task, new chat.</p>



<p class="wp-block-paragraph">Yes, it sounds simple.</p>



<p class="wp-block-paragraph">Yes, it works.</p>



<p class="wp-block-paragraph">And yes, it also helps your human brain, which sometimes has a smaller context window than the model.</p>



<h2 class="wp-block-heading">4. Use&nbsp;<code>/compact</code>&nbsp;and&nbsp;<code>/fork</code>&nbsp;when it makes sense</h2>



<p class="wp-block-paragraph">When a conversation has useful context but starts getting too large, you do not always need to throw it away.</p>



<p class="wp-block-paragraph">You can compact it or fork it.</p>



<p class="wp-block-paragraph">VS Code documents new sessions, forking, and compaction as ways to manage context and reduce unnecessary tokens.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://code.visualstudio.com/docs/copilot/guides/optimize-usage">https://code.visualstudio.com/docs/copilot/guides/optimize-usage</a></p>



<p class="wp-block-paragraph">Good practices:</p>



<ul class="wp-block-list">
<li>use&nbsp;<code>/compact</code>&nbsp;when the conversation has useful information but too much history</li>



<li>use&nbsp;<code>/fork</code>&nbsp;when you want to explore an alternative without polluting the main conversation</li>



<li>start a new chat if the new task is unrelated</li>



<li>summarize the current state before continuing a long task</li>
</ul>



<p class="wp-block-paragraph">Useful prompt:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Summarize the current state, decisions made, files changed, and next steps. Keep it short and actionable.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then copy that summary into a new conversation and continue with clean context.</p>



<p class="wp-block-paragraph">Less noise. Fewer tokens. Better focus.</p>



<h2 class="wp-block-heading">5. Do not ask it to analyze the whole repo if you only need three files</h2>



<p class="wp-block-paragraph">This is the classic one.</p>



<p class="wp-block-paragraph">Expensive prompt:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Analyze this entire repository and tell me what is wrong.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Better prompt:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Analyze only these files:</div><div class="cm-line">- src/MyApp.Api/Program.cs</div><div class="cm-line">- src/MyApp.Core/Services/OrderService.cs</div><div class="cm-line">- tests/MyApp.Tests/OrderServiceTests.cs</div><div class="cm-line"></div><div class="cm-line">Goal: find why this test is failing.</div><div class="cm-line">Do not edit files yet. First explain the likely cause.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The difference is huge.</p>



<p class="wp-block-paragraph">The first prompt invites the model to explore, read, search, open files, infer architecture, and consume context.</p>



<p class="wp-block-paragraph">The second prompt defines:</p>



<ul class="wp-block-list">
<li>files</li>



<li>goal</li>



<li>limits</li>



<li>working mode</li>



<li>expected output</li>
</ul>



<p class="wp-block-paragraph">In AI coding, scope is part of the prompt.</p>



<p class="wp-block-paragraph">Small scope, better result.</p>



<p class="wp-block-paragraph">Infinite scope, surprise in the bill.</p>



<h2 class="wp-block-heading">6. Separate planning, implementation, and validation</h2>



<p class="wp-block-paragraph">One of the most common mistakes with agent mode is asking for everything at once:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Analyze the issue, design the fix, implement it, run tests, fix errors, update docs, and create a summary.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That sounds productive.</p>



<p class="wp-block-paragraph">But it can also trigger loops, tool calls, unnecessary changes, and high token consumption.</p>



<p class="wp-block-paragraph">A better approach is to use phases.</p>



<h3 class="wp-block-heading">Phase 1: plan</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Create a short implementation plan. Do not modify files yet.</div><div class="cm-line">Focus only on the failing test and the minimal code path required to fix it.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Phase 2: scoped implementation</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Implement step 1 only. Modify only the files listed in the plan.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Phase 3: validation</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Run the relevant tests only. If they fail, explain the failure before changing code again.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Phase 4: cleanup</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Now clean up the implementation without changing behavior. Keep the diff small.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The VS Code documentation also recommends planning before implementation to reduce rework and back-and-forth.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://code.visualstudio.com/docs/copilot/guides/optimize-usage">https://code.visualstudio.com/docs/copilot/guides/optimize-usage</a></p>



<p class="wp-block-paragraph">Sometimes the best prompt is not “do everything.”</p>



<p class="wp-block-paragraph">It is “think first, touch little, validate quickly.”</p>



<h2 class="wp-block-heading">7. Be careful with logs: do not paste a novel if you only need the error</h2>



<p class="wp-block-paragraph">Logs are one of the silent token killers.</p>



<p class="wp-block-paragraph">Typical example:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Here is my build log</div></code></pre>
		</div>
	</div>
</div>

<p>[paste 2,000 lines]</p>



<p class="wp-block-paragraph">And the real error was in the last 20 lines.</p>



<p class="wp-block-paragraph">Better:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Here are the last 40 lines of the failing build log. Focus on the first real error, not the cascading errors.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Or even:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">This is the error:</div><div class="cm-line">CS0246: The type or namespace name &apos;X&apos; could not be found.</div><div class="cm-line">Relevant files:</div><div class="cm-line">- Program.cs</div><div class="cm-line">- MyService.cs</div><div class="cm-line"></div><div class="cm-line">What is the likely fix?</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Good practices:</p>



<ul class="wp-block-list">
<li>paste only the first relevant error</li>



<li>avoid full logs if errors are repeated</li>



<li>remove timestamps if they do not add value</li>



<li>remove duplicated stack traces</li>



<li>summarize what you already tried</li>



<li>include exact commands, not the whole terminal history</li>
</ul>



<p class="wp-block-paragraph">Copilot can help a lot with logs.</p>



<p class="wp-block-paragraph">But it does not need to read your CI/CD diary.</p>



<h2 class="wp-block-heading">8. Review your custom instructions</h2>



<p class="wp-block-paragraph">Custom instructions are fantastic.</p>



<p class="wp-block-paragraph">They can also become a token backpack if nobody maintains them.</p>



<p class="wp-block-paragraph">A good&nbsp;<code>.github/copilot-instructions.md</code>&nbsp;file is:</p>



<ul class="wp-block-list">
<li>short</li>



<li>specific</li>



<li>current</li>



<li>based on real repository rules</li>



<li>clear about build/test commands</li>



<li>clear about important conventions</li>
</ul>



<p class="wp-block-paragraph">A bad one is:</p>



<ul class="wp-block-list">
<li>too long</li>



<li>duplicated</li>



<li>contradictory</li>



<li>based on old architecture</li>



<li>full of rules nobody follows</li>



<li>full of generic instructions that apply to every repo on the planet</li>
</ul>



<p class="wp-block-paragraph">Example of useful instructions:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Copilot instructions</div><div class="cm-line"></div><div class="cm-line">- Use C# 13 and .NET 10 conventions.</div><div class="cm-line">- Keep changes minimal and focused.</div><div class="cm-line">- Do not introduce new dependencies without explaining why.</div><div class="cm-line">- Run `dotnet build -c Release` after code changes.</div><div class="cm-line">- Run relevant tests only unless asked for the full suite.</div><div class="cm-line">- Prefer Aspire service defaults when adding services.</div><div class="cm-line">- Do not modify generated files.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">You do not need to write a national constitution for Copilot to understand your repo.</p>



<p class="wp-block-paragraph">You need short rules that reduce repeated decisions.</p>



<h2 class="wp-block-heading">9. Review the MCP servers and tools you have enabled</h2>



<p class="wp-block-paragraph">This is one of the areas where many developers may be consuming context without realizing it.</p>



<p class="wp-block-paragraph">MCP is powerful because it lets agents connect to tools, resources, prompts, and external systems. But every server and every available tool can also affect context, tool selection, and the way the agent works.</p>



<p class="wp-block-paragraph">The VS Code documentation explains that MCP servers can expose tools, resources, prompts, and apps. It also allows developers to enable, disable, install, configure, and manage MCP servers from VS Code.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers">https://code.visualstudio.com/docs/copilot/customization/mcp-servers</a></p>



<p class="wp-block-paragraph">Practical recommendations:</p>



<ul class="wp-block-list">
<li>do not keep every MCP server enabled all the time</li>



<li>enable only what you need for the current workspace</li>



<li>disable experimental MCP servers when you are not using them</li>



<li>review duplicated or overly generic tools</li>



<li>review tool descriptions: if they are too long or confusing, they may hurt tool selection</li>



<li>avoid MCP servers that return huge responses by default</li>



<li>limit resources that add too much context</li>



<li>check whether a server is bringing more information than needed</li>



<li>review MCP logs when something behaves strangely</li>
</ul>



<p class="wp-block-paragraph">Example:</p>



<p class="wp-block-paragraph">If you are working on a local .NET API, maybe you do not need all of these enabled at the same time:</p>



<ul class="wp-block-list">
<li>browser automation</li>



<li>extended filesystem</li>



<li>cloud docs search</li>



<li>GitHub</li>



<li>Jira</li>



<li>Slack</li>



<li>database explorer</li>



<li>Kubernetes</li>



<li>Playwright</li>



<li>internal wiki</li>
</ul>



<p class="wp-block-paragraph">Every extra tool can be useful.</p>



<p class="wp-block-paragraph">But it can also expand the agent’s decision space.</p>



<p class="wp-block-paragraph">And when an agent has too many tools, sometimes the problem is not lack of capability. It is too many temptations.</p>



<p class="wp-block-paragraph">My personal rule:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">MCP servers should be workspace-specific, not personality traits.</p>
</blockquote>



<p class="wp-block-paragraph">Enable what you need. Turn off what you do not.</p>



<h2 class="wp-block-heading">10. Use traditional tools for traditional work</h2>



<p class="wp-block-paragraph">Not everything needs AI.</p>



<p class="wp-block-paragraph">For many tasks, traditional tools are better, faster, and cheaper:</p>



<ul class="wp-block-list">
<li>formatter for formatting</li>



<li>linter for style</li>



<li>compiler for type errors</li>



<li>tests for validation</li>



<li>static analyzers for known rules</li>



<li>dependency scanners for known vulnerabilities</li>



<li>scripts for repeatable tasks</li>
</ul>



<p class="wp-block-paragraph">Copilot is excellent for reasoning, explaining, proposing, connecting ideas, and accelerating implementation.</p>



<p class="wp-block-paragraph">But if you use a frontier model to discover a missing&nbsp;<code>using</code>, something went sideways.</p>



<p class="wp-block-paragraph">Good pattern:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Run the build. Give Copilot only the first relevant compiler error. Ask for the minimal fix.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Bad pattern:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Ask Copilot to inspect the entire repository and find why the build might fail.</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">First, let deterministic tools do their job.</p>



<p class="wp-block-paragraph">Then use AI where it adds value.</p>



<h2 class="wp-block-heading">11. Local models: they do not replace Copilot, but they can complement it very well</h2>



<p class="wp-block-paragraph">We are going to see more PCs with interesting local AI capabilities: stronger GPUs, NPUs, compact workstations, and machines designed to run models locally. NVIDIA, for example, positions RTX Spark as compact PCs and laptops with NVIDIA AI and RTX graphics capabilities.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://www.nvidia.com/en-us/products/rtx-spark/">https://www.nvidia.com/en-us/products/rtx-spark/</a></p>



<p class="wp-block-paragraph">This raises an interesting question:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Does everything need to go to the cloud?</p>
</blockquote>



<p class="wp-block-paragraph">Not necessarily.</p>



<p class="wp-block-paragraph">There are tasks where a local model may be enough:</p>



<ul class="wp-block-list">
<li>summarizing logs</li>



<li>generating documentation drafts</li>



<li>explaining small code snippets</li>



<li>creating scaffolding</li>



<li>generating initial tests</li>



<li>transforming text</li>



<li>preparing prompts</li>



<li>analyzing snippets</li>



<li>creating session summaries</li>



<li>reviewing basic style</li>
</ul>



<p class="wp-block-paragraph">And there are tasks where cloud/frontier models still make a lot of sense:</p>



<ul class="wp-block-list">
<li>complex multi-file debugging</li>



<li>deep reasoning</li>



<li>large migrations</li>



<li>high-risk refactors</li>



<li>architecture</li>



<li>long agentic workflows</li>



<li>direct integration with GitHub, PRs, issues, and CI</li>
</ul>



<p class="wp-block-paragraph">The idea is not “local vs cloud.”</p>



<p class="wp-block-paragraph">The idea is&nbsp;<strong>local for simple work, cloud for work that really needs cloud</strong>.</p>



<p class="wp-block-paragraph">This post is not about advanced BYOK, routing, or gateway architectures. That deserves its own post.</p>



<p class="wp-block-paragraph">But as a baseline idea: if you can solve repetitive tasks with local models, you can reserve Copilot and powerful models for the tasks where they really shine.</p>



<h2 class="wp-block-heading">12. Code review: use it where it adds the most value</h2>



<p class="wp-block-paragraph">Copilot code review can be very useful, but it also has a cost.</p>



<p class="wp-block-paragraph">GitHub documentation explains that Copilot code review is billed in two ways: token consumption through AI Credits and GitHub Actions minutes for the agentic review infrastructure.</p>



<p class="wp-block-paragraph">Source:&nbsp;<a href="https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing">https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing</a></p>



<p class="wp-block-paragraph">Recommendations:</p>



<ul class="wp-block-list">
<li>do not enable high-effort automatic review for every PR without measuring it</li>



<li>use linters and analyzers for mechanical rules</li>



<li>reserve Copilot review for meaningful PRs</li>



<li>define when to use standard vs higher effort</li>



<li>avoid full AI review if you only changed documentation</li>



<li>review usage by repository/team if you are in an organization</li>
</ul>



<p class="wp-block-paragraph">Copilot review can be excellent.</p>



<p class="wp-block-paragraph">But you do not need an AI reviewer philosophically inspecting a three-line README change.</p>



<h2 class="wp-block-heading">13. Define usage profiles for your team</h2>



<p class="wp-block-paragraph">In enterprise teams, the worst-case scenario is letting everyone use any model, in any mode, for any task, with no guidance.</p>



<p class="wp-block-paragraph">You do not need to block everything.</p>



<p class="wp-block-paragraph">You need to educate people and provide clear profiles.</p>



<h3 class="wp-block-heading">Daily coding profile</h3>



<ul class="wp-block-list">
<li>Auto Model Selection</li>



<li>autocomplete and Next Edit Suggestions first</li>



<li>short chat sessions</li>



<li>limited context</li>



<li>lightweight models for simple questions</li>
</ul>



<h3 class="wp-block-heading">Debugging profile</h3>



<ul class="wp-block-list">
<li>minimal logs</li>



<li>first relevant error</li>



<li>specific files</li>



<li>plan before changes</li>



<li>relevant tests, not always the full suite</li>
</ul>



<h3 class="wp-block-heading">Refactor profile</h3>



<ul class="wp-block-list">
<li>plan first</li>



<li>scope by folder or feature</li>



<li>changes in phases</li>



<li>more powerful model only during the hard part</li>



<li>frequent validation</li>
</ul>



<h3 class="wp-block-heading">Documentation profile</h3>



<ul class="wp-block-list">
<li>lightweight or local model</li>



<li>specific files</li>



<li>limited output</li>



<li>no agent mode unless needed</li>
</ul>



<h3 class="wp-block-heading">Agent mode profile</h3>



<ul class="wp-block-list">
<li>clear issue</li>



<li>clear stop condition</li>



<li>defined scope</li>



<li>defined validation commands</li>



<li>do not let it run unsupervised if the goal is ambiguous</li>
</ul>



<p class="wp-block-paragraph">This is not about saying “do not use Copilot.”</p>



<p class="wp-block-paragraph">It is about saying “use the right mode for the right job.”</p>



<h2 class="wp-block-heading">14. Quick checklist for developers</h2>



<p class="wp-block-paragraph">Before sending the next prompt, ask yourself:</p>



<ul class="wp-block-list">
<li>Can I solve this with autocomplete?</li>



<li>Do I need chat, or is inline edit enough?</li>



<li>Do I need agent mode, or just an explanation?</li>



<li>Am I using Auto, or am I using a model that is too expensive for the task?</li>



<li>Does this conversation already have too much history?</li>



<li>Should I start a new chat?</li>



<li>Can I pass only two or three files?</li>



<li>Can I paste only the relevant error?</li>



<li>Can I ask for a plan first?</li>



<li>Do I have MCP servers enabled that I do not need?</li>



<li>Are my custom instructions too long?</li>



<li>Can a linter/test/build answer this before AI?</li>



<li>Could this task go to a local model?</li>
</ul>



<p class="wp-block-paragraph">If the answer is “yes” to several of these, you can probably save tokens without losing productivity.</p>



<h2 class="wp-block-heading">15. Checklist for teams</h2>



<p class="wp-block-paragraph">For organizations, I would start here:</p>



<ul class="wp-block-list">
<li>review usage by user, team, and repository</li>



<li>understand which models are used the most</li>



<li>review how much usage comes from agent mode</li>



<li>review Copilot code review usage</li>



<li>define internal model selection guidelines</li>



<li>promote Auto as the default</li>



<li>teach small, scoped prompts</li>



<li>clean up custom instructions by repository</li>



<li>review recommended and allowed MCP servers</li>



<li>create usage profiles by task type</li>



<li>measure before and after changes</li>



<li>do not block AI out of fear</li>



<li>govern it like any other cloud resource</li>
</ul>



<p class="wp-block-paragraph">Because this looks a lot like cloud cost optimization.</p>



<p class="wp-block-paragraph">First, everyone celebrated how easy it was to create resources.</p>



<p class="wp-block-paragraph">Then the bill arrived.</p>



<p class="wp-block-paragraph">Then we learned FinOps.</p>



<p class="wp-block-paragraph">Now we need something similar for AI-assisted development.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">The new GitHub Copilot usage model does not mean we need to stop using AI to code.</p>



<p class="wp-block-paragraph">It means we can no longer treat every interaction as free, infinite, and invisible.</p>



<p class="wp-block-paragraph">The good news is that many optimizations are simple:</p>



<ul class="wp-block-list">
<li>use autocomplete before chat</li>



<li>choose the model intentionally</li>



<li>start new chats</li>



<li>reduce context</li>



<li>limit logs</li>



<li>separate planning and implementation</li>



<li>review MCP servers</li>



<li>clean up custom instructions</li>



<li>use traditional tools when they fit</li>



<li>reserve powerful models for powerful problems</li>



<li>consider local models for simple tasks</li>
</ul>



<p class="wp-block-paragraph">The goal is not to use less Copilot.</p>



<p class="wp-block-paragraph">The goal is to use fewer tokens and get better results.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/06/04/github-copilot-and-tokens-how-to-keep-using-ai-without-burning-your-budget-in-three-prompts-some-personal-lessons-learned/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40745</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-5-2026-08_55_47-amsmall.png">
			<media:title type="html">ChatGPT Image Jun 5, 2026, 08_55_47 AM=small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>MAI-Image-2.5 support in ElBruno.Text2Image and the t2i CLI &#128640;</title>
		<link>https://elbruno.com/2026/06/03/mai-image-2-5-support-in-elbruno-text2image-and-the-t2i-cli-%f0%9f%9a%80/</link>
					<comments>https://elbruno.com/2026/06/03/mai-image-2-5-support-in-elbruno-text2image-and-the-t2i-cli-%f0%9f%9a%80/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 03 Jun 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Foundry]]></category>
		<category><![CDATA[MAI-Image]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[Text-To-Image]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40739</guid>

					<description><![CDATA[Hi! And for the Flash version, use the same generator with the Flash model id:]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img width="1024" height="576" src="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=1024" alt="" class="wp-image-40743" srcset="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png 1672w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<div class="wp-block-jetpack-markdown"><p>Microsoft Build brought a wave of new AI model announcements, and one of the fun ones for image generation is <strong>MAI-Image-2.5</strong>.</p>
<p>So, of course, I had to add support for it in my .NET image generation library and CLI tool <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>The latest version of <strong>ElBruno.Text2Image</strong> now supports:</p>
<ul>
<li><strong>MAI-Image-2.5</strong></li>
<li><strong>MAI-Image-2.5-Flash</strong></li>
</ul>
<p>This means you can generate images using the new MAI models from a .NET app or directly from the terminal with the <code>t2i</code> CLI.</p>
<h2>Links</h2>
<p>Official model announcement:</p>
<p><a href="https://microsoft.ai/models/mai-image-2-5/" rel="nofollow">https://microsoft.ai/models/mai-image-2-5/</a></p>
<p>NuGet packages:</p>
<ul>
<li>ElBruno.Text2Image: <a href="https://www.nuget.org/packages/ElBruno.Text2Image" rel="nofollow">https://www.nuget.org/packages/ElBruno.Text2Image</a></li>
<li>ElBruno.Text2Image.Cli: <a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli" rel="nofollow">https://www.nuget.org/packages/ElBruno.Text2Image.Cli</a></li>
</ul>
<p>Source code:</p>
<p><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></p>
<h2>Install the CLI</h2>
<pre><code class="language-bash">dotnet tool install --global ElBruno.Text2Image.Cli
</code></pre>
<p>Or update it if you already have it installed:</p>
<pre><code class="language-bash">dotnet tool update --global ElBruno.Text2Image.Cli
</code></pre>
<h2>Generate an image from the terminal</h2>
<p>Using MAI-Image-2.5:</p>
<pre><code class="language-bash">t2i --provider foundry-mai25 &quot;a photograph of a red fox in an autumn forest&quot;
</code></pre>
<p>Using MAI-Image-2.5-Flash:</p>
<pre><code class="language-bash">t2i --provider foundry-mai25-flash &quot;a quick concept sketch of a city skyline&quot;
</code></pre>
<p>That’s the part I like the most:</p>
<pre><code class="language-text">prompt -&gt; terminal -&gt; image
</code></pre>
<p>Simple and fun.</p>
<h2>Use it from C#</h2>
<p>You can also use the new MAI-Image-2.5 model from a .NET app.</p>
</div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.Text2Image;
using ElBruno.Text2Image.Foundry;

using var generator = new MaiImage25Generator(
    endpoint: "https://your-resource.services.ai.azure.com",
    apiKey: "your-api-key",
    httpClient: new HttpClient(),
    modelName: "MAI-Image-2.5",
    modelId: "MAI-Image-2.5");

var result = await generator.GenerateAsync(
    "A photograph of a red fox in an autumn forest");

await result.SaveAsync("mai-image25-output.png");
</pre></div>


<p class="wp-block-paragraph">And for the Flash version, use the same generator with the Flash model id:<br /></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.Text2Image;
using ElBruno.Text2Image.Foundry;

using var generator = new MaiImage25Generator(
    endpoint: "https://your-resource.services.ai.azure.com",
    apiKey: "your-api-key",
    httpClient: new HttpClient(),
    modelName: "MAI-Image-2.5-Flash",
    modelId: "MAI-Image-2.5-Flash");

var result = await generator.GenerateAsync(
    "A clean comic-style illustration of a happy developer building AI apps");

await result.SaveAsync("mai-image25-flash-output.png");
</pre></div>


<div class="wp-block-jetpack-markdown"><h2>Why this is useful</h2>
<p>The goal of this project is simple:</p>
<ul>
<li>Use image generation from .NET</li>
<li>Support cloud and local models</li>
<li>Keep the API clean</li>
<li>Make the CLI useful for developers, demos, agents, and automation</li>
<li>Avoid needing a Python environment for every image generation experiment</li>
</ul>
<p>And now with MAI-Image-2.5 and MAI-Image-2.5-Flash, we have more options to experiment with image generation from .NET and from the command line.</p>
<p>Build announcements are fun.
Build announcements that I can use from a terminal?
Even better <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f60e.png" alt="😎" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/06/03/mai-image-2-5-support-in-elbruno-text2image-and-the-t2i-cli-%f0%9f%9a%80/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40739</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png">
			<media:title type="html">ChatGPT Image Jun 2, 2026, 09_00_33 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/06/chatgpt-image-jun-2-2026-09_00_33-pm.png?w=1024"/>
	</item>
		<item>
		<title>VS Code 1.122 Makes BYOK Easier</title>
		<link>https://elbruno.com/2026/05/30/vs-code-1-122-makes-byok-easier/</link>
					<comments>https://elbruno.com/2026/05/30/vs-code-1-122-makes-byok-easier/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Sun, 31 May 2026 00:52:45 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[BYOK]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub Copilot]]></category>
		<category><![CDATA[Visual Studio Code]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40732</guid>

					<description><![CDATA[One of the most interesting announcements in the recent VS Code 1.122 release is that Bring Your Own Key (BYOK) models can now be used without signing in to GitHub. VS Code now supports BYOK scenarios, including chat, tools, and MCP integrations, without requiring GitHub authentication, making enterprise, offline, and air-gapped workflows much easier to [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img width="1024" height="576" src="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=1024" alt="" class="wp-image-40736" srcset="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png 1672w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">One of the most interesting announcements in the recent VS Code 1.122 release is that Bring Your Own Key (BYOK) models can now be used <strong>without signing in to GitHub</strong>. VS Code now supports BYOK scenarios, including chat, tools, and MCP integrations, without requiring GitHub authentication, making enterprise, offline, and air-gapped workflows much easier to support.</p>



<p class="wp-block-paragraph">I originally came across this update thanks to a tweet from Pierce Boggan:</p>



<figure class="wp-block-embed is-type-rich is-provider-x wp-block-embed-x"><div class="wp-block-embed__wrapper">
<div class="embed-x"><blockquote class="twitter-tweet" data-width="500" data-dnt="true"><p lang="en" dir="ltr">VS Code gives you: <br><br>1. SOTA models from Anthropic, OpenAI, Gemini<br>2. BYOK for any model not natively supported in the product (and works w/o login!) <br>3. Harness choice with VS Code, Copilot CLI, Copilot cloud agent, and 3p harnesses like Claude/Codex <br><br>Developer choice ftw! <a href="https://t.co/mm7n1owQK5">https://t.co/mm7n1owQK5</a></p>&mdash; Pierce Boggan (@pierceboggan) <a href="https://x.com/pierceboggan/status/2060837045195649345?ref_src=twsrc%5Etfw">May 30, 2026</a></blockquote><script async src="https://platform.x.com/widgets.js" charset="utf-8"></script></div>
</div></figure>



<p class="wp-block-paragraph">This immediately caught my attention because it connects directly with the experiments I’ve been running around GitHub Copilot CLI, local models, BYOK, SQUAD, and AI-assisted software engineering.</p>



<p class="wp-block-paragraph">Quick recap:</p>



<p class="wp-block-paragraph">CPU-only local models<br />Slow, useful mostly for questions and tiny tasks.<br /><a href="https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/">https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/</a></p>



<p class="wp-block-paragraph">GPU local models<br />Faster, with more room for local agent experiments.<br /><a href="https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/">https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/</a></p>



<p class="wp-block-paragraph">GPT-5-mini BYOK<br />Capable, but stabilization, quality gates, and validation became the real work.<br /><a href="https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/">https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/</a></p>



<p class="wp-block-paragraph">GPT-5.5 BYOK<br />The most disciplined run so far, with better phase control, quality gates, and manual UX validation.<br /><a href="https://elbruno.com/2026/05/14/github-copilot-cli-squad-gpt-5-5-byok-better-engineering-same-hard-truth/">https://elbruno.com/2026/05/14/github-copilot-cli-squad-gpt-5-5-byok-better-engineering-same-hard-truth/</a></p>



<p class="wp-block-paragraph">The new VS Code 1.122 BYOK flow feels like a natural next step for this series.</p>



<p class="wp-block-paragraph">I haven’t rerun the ElBruno.NetAgent experiment with this new capability yet, but it is definitely on my list.</p>



<p class="wp-block-paragraph">The lesson remains the same:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The code keeps getting cheaper. Validation and engineering discipline are still expensive.</p>
</blockquote>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/30/vs-code-1-122-makes-byok-easier/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40732</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png">
			<media:title type="html">ChatGPT Image May 30, 2026, 08_49_35 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-30-2026-08_49_35-pm.png?w=1024"/>
	</item>
		<item>
		<title>Building Cross-Framework Agents with MAF, A2A, NVIDIA NeMo, and Aspire</title>
		<link>https://elbruno.com/2026/05/20/building-cross-framework-agents-with-maf-a2a-nvidia-nemo-and-aspire/</link>
					<comments>https://elbruno.com/2026/05/20/building-cross-framework-agents-with-maf-a2a-nvidia-nemo-and-aspire/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 20 May 2026 15:22:19 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[A2A]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[MAF]]></category>
		<category><![CDATA[Nemo Agent Toolkit]]></category>
		<category><![CDATA[NVidia]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40725</guid>

					<description><![CDATA[Hi! What happens when a Python-based AI agent and a .NET-based AI agent need to work together? That is the idea behind MAF-A2A-NVIDIA-NemoAgents (https://github.com/elbruno/MAF-A2A-NVIDIA-NemoAgents-private): a reference app that shows how to combine NVIDIA NeMo Agent Toolkit, Microsoft Agent Framework, Agent-to-Agent communication, and Aspire into one multi-agent workflow. The repo describes itself as a production-ready sample [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="836" height="471" src="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png?w=836" alt="" class="wp-image-40730" srcset="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png 836w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png?w=768 768w" sizes="(max-width: 836px) 100vw, 836px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">What happens when a Python-based AI agent and a .NET-based AI agent need to work together?</p>



<p class="wp-block-paragraph">That is the idea behind <strong><a href="https://github.com/elbruno/MAF-A2A-NVIDIA-NemoAgents-private" target="_blank" rel="noopener">MAF-A2A-NVIDIA-NemoAgents</a></strong> (<a href="https://github.com/elbruno/MAF-A2A-NVIDIA-NemoAgents-private">https://github.com/elbruno/MAF-A2A-NVIDIA-NemoAgents-private</a>): a reference app that shows how to combine <strong>NVIDIA NeMo Agent Toolkit</strong>, <strong>Microsoft Agent Framework</strong>, <strong>Agent-to-Agent communication</strong>, and <strong>Aspire</strong> into one multi-agent workflow. The repo describes itself as a production-ready sample that demonstrates NVIDIA NeMo Agent Toolkit + Microsoft Agent Framework with A2A communication, orchestrated with Aspire.</p>



<p class="wp-block-paragraph">And yes, this is exactly the kind of demo I like: different stacks, different responsibilities, one shared protocol. No “everything must be rewritten in my favorite framework” drama. Just agents talking to agents. Beautiful.</p>



<h2 class="wp-block-heading">The scenario: analysis first, action second</h2>



<p class="wp-block-paragraph">The application models a practical enterprise workflow:</p>



<p class="wp-block-paragraph">A <strong>NeMo Data Analysis Agent</strong> receives business or operational data, analyzes metrics, detects trends or anomalies, and generates insights. Then, when an action is needed, a <strong>MAF Action Agent</strong> can trigger alerts, generate reports, or execute remediation steps. The web UI acts as the human-facing chat surface where users can request analysis and trigger actions.</p>



<p class="wp-block-paragraph">So the flow is simple:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">User  </div><div class="cm-line">↓ Web Chat UI  </div><div class="cm-line">↓ NeMo Data Analysis Agent  </div><div class="cm-line">↓ Analysis result  </div><div class="cm-line">↓ MAF Action Agent  </div><div class="cm-line">↓ Alert / Report / Action</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The interesting part is that these agents are not built with the same framework. The NeMo agent is Python-based. The MAF agent is .NET-based. The bridge between them is <strong>A2A</strong>, using JSON-RPC and agent discovery. The repo architecture describes A2A as the communication layer between the web UI, the NeMo agent, and the MAF agent, with Aspire providing health checks, service discovery, tracing, and startup orchestration.</p>



<h2 class="wp-block-heading">Why two agents?</h2>



<p class="wp-block-paragraph">Because “one mega-agent to rule them all” sounds cool until you need to debug, scale, secure, or explain it.</p>



<p class="wp-block-paragraph">This repo separates responsibilities:</p>



<ul class="wp-block-list">
<li><strong>NeMo Agent</strong>: data analysis, trends, anomalies, metrics, recommendations.</li>



<li><strong>MAF Agent</strong>: actions, alerts, reports, remediation workflows.</li>



<li><strong>Web UI</strong>: chat experience, discovery, orchestration, response rendering.</li>



<li><strong>Aspire</strong>: local distributed app orchestration, health, logs, and OpenTelemetry traces.</li>
</ul>



<p class="wp-block-paragraph">The repo calls out this separation of concerns directly: data analysis expertise is not the same as action execution expertise. It also helps with scalability, reliability, and vendor flexibility.</p>



<h2 class="wp-block-heading">Where A2A fits</h2>



<p class="wp-block-paragraph">A2A is the key enabler here. It gives agents a common way to expose capabilities and communicate across frameworks and languages.</p>



<p class="wp-block-paragraph">On the Microsoft Agent Framework side, A2A support allows agents to be exposed over A2A endpoints and discovered through agent cards. Microsoft’s A2A docs show that Agent Framework can expose multiple agents with separate A2A endpoints, and the <code>agent-framework-a2a</code> package can both connect to external A2A-compliant agents and expose Agent Framework agents over A2A.</p>



<p class="wp-block-paragraph">The .NET A2A pattern looks like this in the official MAF examples:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">AddA2AServer</span>(<span class="tok-string">&quot;weather-agent&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapA2AHttpJson</span>(<span class="tok-string">&quot;weather-agent&quot;</span>, <span class="tok-string">&quot;/a2a/weather-agent&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapWellKnownAgentCard</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">AgentCard</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Name</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;WeatherAgent&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">Description</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;A helpful weather assistant.&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">SupportedInterfaces</span> <span class="tok-operator">=</span></div><div class="cm-line">    [</div><div class="cm-line">        <span class="tok-keyword">new</span> <span class="tok-variableName">AgentInterface</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">Url</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;https://your-host/a2a/weather-agent&quot;</span>,</div><div class="cm-line">            <span class="tok-variableName">ProtocolBinding</span> <span class="tok-operator">=</span> <span class="tok-variableName">ProtocolBindingNames</span>.<span class="tok-variableName">HttpJson</span>,</div><div class="cm-line">            <span class="tok-variableName">ProtocolVersion</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;1.0&quot;</span>,</div><div class="cm-line">        }</div><div class="cm-line">    ]</div><div class="cm-line">});</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That pattern matters because agent discovery is not a nice-to-have. In a multi-agent system, the orchestrator needs to know:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Who are you?</div><div class="cm-line"></div><div class="cm-line">What can you do?</div><div class="cm-line"></div><div class="cm-line">Where do I call you?</div><div class="cm-line"></div><div class="cm-line">Which protocol do you speak?</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That is where the <strong>Agent Card</strong> becomes useful.</p>



<h2 class="wp-block-heading">NeMo as an A2A agent</h2>



<p class="wp-block-paragraph">On the NVIDIA side, NeMo Agent Toolkit can publish workflows as A2A agents. NVIDIA’s docs describe <code>nat a2a serve</code> as the command that starts an A2A server, publishes a workflow as an A2A agent, and exposes an Agent Card at <code>/.well-known/agent-card.json</code>.</p>



<p class="wp-block-paragraph">In this repo, Aspire starts the NeMo workflow using the NeMo Agent Toolkit A2A server approach. The AppHost launches the NeMo agent as an executable, configures the workflow file, host, port, public base URL, NVIDIA API key, Azure OpenAI settings, and OpenTelemetry exporter.</p>



<p class="wp-block-paragraph">A simplified version of that idea looks like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">nvidiaApiKey</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddParameter</span>(<span class="tok-string">&quot;nvidia-api-key&quot;</span>, <span class="tok-variableName">secret</span>: <span class="tok-atom">true</span>);</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">azureOpenAiEndpoint</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddParameter</span>(<span class="tok-string">&quot;azure-openai-endpoint&quot;</span>, <span class="tok-variableName">secret</span>: <span class="tok-atom">true</span>);</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">azureOpenAiApiKey</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddParameter</span>(<span class="tok-string">&quot;azure-openai-api-key&quot;</span>, <span class="tok-variableName">secret</span>: <span class="tok-atom">true</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">nemo</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddExecutable</span>(</div><div class="cm-line">        <span class="tok-variableName">name</span>: <span class="tok-string">&quot;nemo-agent&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">command</span>: <span class="tok-string">&quot;powershell&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">workingDirectory</span>: <span class="tok-string">&quot;.&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">args</span>:</div><div class="cm-line">        [</div><div class="cm-line">            <span class="tok-string">&quot;-NoProfile&quot;</span>,</div><div class="cm-line">            <span class="tok-string">&quot;-Command&quot;</span>,</div><div class="cm-line">            <span class="tok-string">&quot;nat a2a serve --config_file .\\src\\NemoDataAnalysisAgent\\nemo\\workflow.yml&quot;</span></div><div class="cm-line">        ])</div><div class="cm-line">    .<span class="tok-variableName">WithHttpEndpoint</span>(<span class="tok-variableName">name</span>: <span class="tok-string">&quot;http&quot;</span>, <span class="tok-variableName">env</span>: <span class="tok-string">&quot;NEMO_PORT&quot;</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithEnvironment</span>(<span class="tok-string">&quot;NEMO_HOST&quot;</span>, <span class="tok-string">&quot;127.0.0.1&quot;</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithEnvironment</span>(<span class="tok-string">&quot;NVIDIA_API_KEY&quot;</span>, <span class="tok-variableName">nvidiaApiKey</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithEnvironment</span>(<span class="tok-string">&quot;AZURE_OPENAI_ENDPOINT&quot;</span>, <span class="tok-variableName">azureOpenAiEndpoint</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithEnvironment</span>(<span class="tok-string">&quot;AZURE_OPENAI_API_KEY&quot;</span>, <span class="tok-variableName">azureOpenAiApiKey</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithOtlpExporter</span>();</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The important idea: <strong>NeMo owns the analysis workflow, but it is exposed as an A2A-compatible service</strong>.</p>



<h2 class="wp-block-heading">MAF as the action agent</h2>



<p class="wp-block-paragraph">The MAF side is a .NET service focused on action execution. The repo exposes endpoints for executing actions, triggering alerts, generating reports, and handling A2A JSON-RPC requests. The README describes the MAF Action Agent as responsible for pluggable action handlers, multi-level alerts, async report generation, health checks, A2A integration, and OpenTelemetry tracing.</p>



<p class="wp-block-paragraph">A simplified version of the MAF action endpoint looks like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapPost</span>(<span class="tok-string">&quot;/api/actions/execute&quot;</span>,</div><div class="cm-line">    <span class="tok-keyword">async</span> (<span class="tok-variableName">ActionRequest</span> <span class="tok-variableName">request</span>, <span class="tok-variableName">IActionExecutor</span> <span class="tok-variableName">executor</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">result</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">executor</span>.<span class="tok-variableName">ExecuteActionAsync</span>(<span class="tok-variableName">request</span>);</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-variableName">result</span>);</div><div class="cm-line">    });</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And the A2A-facing endpoints follow this shape:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapGet</span>(<span class="tok-string">&quot;/.well-known/agent-card.json&quot;</span>, () <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-keyword">new</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">name</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;MAF Action Agent&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">description</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Executes actions based on NeMo analysis&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">version</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;1.0.0&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">capabilities</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>[]</div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-string">&quot;execute-actions&quot;</span>,</div><div class="cm-line">            <span class="tok-string">&quot;trigger-alerts&quot;</span>,</div><div class="cm-line">            <span class="tok-string">&quot;generate-reports&quot;</span></div><div class="cm-line">        },</div><div class="cm-line">        <span class="tok-variableName">endpoint</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;/a2a/maf-action-agent&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">a2a_version</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;1.0&quot;</span></div><div class="cm-line">    });</div><div class="cm-line">});</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapPost</span>(<span class="tok-string">&quot;/a2a/maf-action-agent&quot;</span>,</div><div class="cm-line">    <span class="tok-keyword">async</span> (<span class="tok-variableName">HttpContext</span> <span class="tok-variableName">context</span>, <span class="tok-variableName">IA2ABridge</span> <span class="tok-variableName">bridge</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">reader</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">StreamReader</span>(<span class="tok-variableName">context</span>.<span class="tok-variableName">Request</span>.<span class="tok-variableName">Body</span>);</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">body</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">reader</span>.<span class="tok-variableName">ReadToEndAsync</span>();</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">response</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">bridge</span>.<span class="tok-variableName">ProcessA2ARequestAsync</span>(<span class="tok-variableName">body</span>);</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Text</span>(<span class="tok-variableName">response</span>, <span class="tok-string">&quot;application/json&quot;</span>);</div><div class="cm-line">    });</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That is the handoff: the agent advertises what it can do, then receives structured requests through the A2A endpoint.</p>



<h2 class="wp-block-heading">The web UI as the orchestrator</h2>



<p class="wp-block-paragraph">The web chat interface is where the human interacts with the system. It discovers both agents, sends analysis requests to NeMo, and optionally sends action requests to MAF. The README calls out a <strong>NeMo-first routing</strong> pattern: chat responses come from NeMo analysis before optional MAF action execution.</p>



<p class="wp-block-paragraph">A simplified orchestration pattern looks like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ChatResponse</span><span class="tok-operator">&gt;</span> <span class="tok-variableName tok-definition">ProcessChatAsync</span>(<span class="tok-variableName">ChatRequest</span> <span class="tok-variableName">request</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">message</span> <span class="tok-operator">=</span> <span class="tok-variableName">request</span>.<span class="tok-variableName">Message</span>.<span class="tok-variableName">ToLowerInvariant</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-variableName">ShouldTriggerAction</span>(<span class="tok-variableName">message</span>))</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">action</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ActionRequest</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">ActionType</span> <span class="tok-operator">=</span> <span class="tok-variableName">InferActionType</span>(<span class="tok-variableName">message</span>),</div><div class="cm-line">            <span class="tok-variableName">Parameters</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">Dictionary</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span>, <span class="tok-typeName">object</span><span class="tok-operator">?&gt;</span></div><div class="cm-line">            {</div><div class="cm-line">                [<span class="tok-string">&quot;message&quot;</span>] <span class="tok-operator">=</span> <span class="tok-variableName">request</span>.<span class="tok-variableName">Message</span></div><div class="cm-line">            }</div><div class="cm-line">        };</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">actionResult</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">orchestrator</span>.<span class="tok-variableName">ExecuteActionAsync</span>(<span class="tok-variableName">action</span>);</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ChatResponse</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">RespondedBy</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;MAF Action Agent&quot;</span>,</div><div class="cm-line">            <span class="tok-variableName">Content</span> <span class="tok-operator">=</span> <span class="tok-variableName">actionResult</span>.<span class="tok-variableName">Details</span></div><div class="cm-line">        };</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">nemoReply</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">orchestrator</span>.<span class="tok-variableName">SendNemoMessageAsync</span>(</div><div class="cm-line">        <span class="tok-variableName">request</span>.<span class="tok-variableName">Message</span>,</div><div class="cm-line">        <span class="tok-variableName">request</span>.<span class="tok-variableName">SessionId</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ChatResponse</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">RespondedBy</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;NeMo Data Analysis Agent&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">Content</span> <span class="tok-operator">=</span> <span class="tok-variableName">nemoReply</span></div><div class="cm-line">    };</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That is a nice pattern for demos and real apps: default to insight, escalate to action only when the user explicitly asks for it.</p>



<h2 class="wp-block-heading">A2A request shape</h2>



<p class="wp-block-paragraph">The architecture docs describe JSON-RPC 2.0 over HTTP as the protocol used between agents. The documented example sends a method such as <code>analyze</code>, passes parameters like metric, timeframe, and aggregation, and receives a structured result with trend, anomalies, forecast, and insights.</p>



<p class="wp-block-paragraph">A simplified A2A-style request could look like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-json"><div class="cm-line"><span class="tok-punctuation">{</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;jsonrpc&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;2.0&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;method&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;message/send&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;params&quot;</span><span class="tok-punctuation">:</span> <span class="tok-punctuation">{</span></div><div class="cm-line">    <span class="tok-propertyName">&quot;message&quot;</span><span class="tok-punctuation">:</span> <span class="tok-punctuation">{</span></div><div class="cm-line">      <span class="tok-propertyName">&quot;kind&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;message&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">      <span class="tok-propertyName">&quot;role&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;user&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">      <span class="tok-propertyName">&quot;parts&quot;</span><span class="tok-punctuation">:</span> <span class="tok-punctuation">[</span></div><div class="cm-line">        <span class="tok-punctuation">{</span></div><div class="cm-line">          <span class="tok-propertyName">&quot;kind&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;text&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">          <span class="tok-propertyName">&quot;text&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;Analyze quarterly revenue trends&quot;</span></div><div class="cm-line">        <span class="tok-punctuation">}</span></div><div class="cm-line">      <span class="tok-punctuation">]</span></div><div class="cm-line">    <span class="tok-punctuation">}</span></div><div class="cm-line">  <span class="tok-punctuation">}</span><span class="tok-punctuation">,</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;id&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;request-001&quot;</span></div><div class="cm-line"><span class="tok-punctuation">}</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And the system can route that to the NeMo agent first. Later, a second request can trigger the MAF agent:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-json"><div class="cm-line"><span class="tok-punctuation">{</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;actionType&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;trigger-alert&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;parameters&quot;</span><span class="tok-punctuation">:</span> <span class="tok-punctuation">{</span></div><div class="cm-line">    <span class="tok-propertyName">&quot;message&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;Trigger alert for high CPU usage&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">    <span class="tok-propertyName">&quot;analysisSummary&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;CPU usage increased 35% over baseline.&quot;</span></div><div class="cm-line">  <span class="tok-punctuation">}</span></div><div class="cm-line"><span class="tok-punctuation">}</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That second part is where things become useful. The action agent is not acting in isolation; it can act based on the previous analysis.</p>



<h2 class="wp-block-heading">Why Aspire matters here</h2>



<p class="wp-block-paragraph">Aspire is doing a lot of the boring-but-important work.</p>



<p class="wp-block-paragraph">The repo uses Aspire to coordinate startup order, service discovery, health checks, logging, and OpenTelemetry correlation across the NeMo agent, the MAF agent, and the web UI. The README describes Aspire as responsible for automatic agent endpoint registration, dependency management, continuous health checks, distributed tracing, unified logs, and NeMo pre-warm.</p>



<p class="wp-block-paragraph">This is one of the hidden wins of the repo. Multi-agent demos often fail because they are hard to run. Here, Aspire gives the app a “one command, multiple services” developer experience:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">aspire start</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The README says that after starting the app, the web UI is available on port <code>5000</code>, and the sample prompts include NeMo-only analysis, MAF-only alert triggering, and a combined NeMo + MAF workflow.</p>



<h2 class="wp-block-heading">Why this repo is interesting</h2>



<p class="wp-block-paragraph">This repo is not just “another chat app.”</p>



<p class="wp-block-paragraph">It demonstrates a few patterns that matter for real enterprise AI systems:</p>



<ol class="wp-block-list">
<li><strong>Cross-framework agents</strong><br />Python and .NET agents can collaborate without forcing everything into one stack.</li>



<li><strong>Clear agent responsibility boundaries</strong><br />One agent analyzes. One agent acts. That makes the architecture easier to explain, test, and evolve.</li>



<li><strong>A2A as the interoperability layer</strong><br />A2A gives each agent a common communication contract.</li>



<li><strong>Aspire as the local cloud-native dev loop</strong><br />Developers can run, observe, and debug the system locally before thinking about containers or cloud deployment.</li>



<li><strong>Observability from the beginning</strong><br />The architecture includes OpenTelemetry tracing, structured logging, and health checks instead of treating them as “we’ll add this later” work. The repo’s architecture highlights call out distributed tracing, resilience patterns, scalability, and a future roadmap for TLS, mutual authentication, and authorization.</li>
</ol>



<h2 class="wp-block-heading">Final thoughts</h2>



<p class="wp-block-paragraph">The big takeaway for me: <strong>A2A makes heterogeneous agent systems feel much more realistic</strong>.</p>



<p class="wp-block-paragraph">In real organizations, not every team will use the same language, same framework, same model provider, or same runtime. Some teams will build in Python. Some will build in .NET. Some will use NVIDIA tooling. Some will use Microsoft Agent Framework. Some will deploy locally first, then move to Azure.</p>



<p class="wp-block-paragraph">And that is fine.</p>



<p class="wp-block-paragraph">The goal is not to force every agent into one framework. The goal is to give them a shared way to discover each other, communicate, and collaborate.</p>



<p class="wp-block-paragraph">That is what this repo demonstrates: <strong>NVIDIA NeMo for analysis, MAF for action, A2A for interoperability, and Aspire for orchestration</strong>.</p>



<p class="wp-block-paragraph">Agents talking to agents.</p>



<p class="wp-block-paragraph">Finally, in a way we can actually run, observe, and explain.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/20/building-cross-framework-agents-with-maf-a2a-nvidia-nemo-and-aspire/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40725</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png">
			<media:title type="html">ChatGPT Image May 20, 2026, 11_20_33 AM-small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-20-2026-11_20_33-am-small.png?w=836"/>
	</item>
		<item>
		<title>GitHub Copilot CLI + SQUAD + GPT-5.5 BYOK: Better Engineering, Same Hard Truth</title>
		<link>https://elbruno.com/2026/05/14/github-copilot-cli-squad-gpt-5-5-byok-better-engineering-same-hard-truth/</link>
					<comments>https://elbruno.com/2026/05/14/github-copilot-cli-squad-gpt-5-5-byok-better-engineering-same-hard-truth/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 14 May 2026 13:28:46 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub Copilot]]></category>
		<category><![CDATA[GitHub Copilot CLI]]></category>
		<category><![CDATA[GPT-5.5]]></category>
		<category><![CDATA[LLM]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40713</guid>

					<description><![CDATA[TL;DR This was the final ElBruno.NetAgent experiment: GitHub Copilot CLI + SQUAD using Azure OpenAI GPT-5.5 BYOK against the same app-building challenge I previously tried with CPU-only local models, GPU local models, and GPT-5-mini. The good news: GPT-5.5 was clearly better at staying inside phase boundaries, following safety rules, reducing broad stabilization loops, and working [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<figure class="wp-block-image size-large"><img loading="lazy" width="836" height="471" src="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png?w=836" alt="" class="wp-image-40714" srcset="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png 836w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png?w=768 768w" sizes="(max-width: 836px) 100vw, 836px" /></figure>



<h2 class="wp-block-heading">TL;DR</h2>



<p class="wp-block-paragraph">This was the final ElBruno.NetAgent experiment: GitHub Copilot CLI + SQUAD using Azure OpenAI GPT-5.5 BYOK against the same app-building challenge I previously tried with CPU-only local models, GPU local models, and GPT-5-mini.</p>



<p class="wp-block-paragraph">The good news: GPT-5.5 was clearly better at staying inside phase boundaries, following safety rules, reducing broad stabilization loops, and working with strict quality gates. Once I switched to economy-mode prompts, the flow became much more disciplined: usually one implementation agent, one reviewer, short loops, and no wild repo-wide rewrites.</p>



<p class="wp-block-paragraph">The not-so-funny news: better models do not remove the need for engineering discipline. Build green, tests green, and smoke tests green still missed a real manual UX bug: the Settings UI looked like it saved Auto Mode, but the persisted config did not actually keep the value. The fix was not “more tests”; it was the right test: one that exercised the real WPF user path, not only the ViewModel.</p>



<p class="wp-block-paragraph">Also: GPT-5.5 BYOK is powerful, but not cheap. By the time the app reached final automated validation, the run was already around 15M+ tokens and roughly $30+ in Azure OpenAI usage. SQUAD is useful, but every agent has a context tax.</p>



<p class="wp-block-paragraph">The biggest lesson: the code is still cheap. The validation strategy is the expensive part.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="688" height="384" src="https://elbruno.com/wp-content/uploads/2026/05/img01-small.png?w=688" alt="" class="wp-image-40717" srcset="https://elbruno.com/wp-content/uploads/2026/05/img01-small.png 688w, https://elbruno.com/wp-content/uploads/2026/05/img01-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/img01-small.png?w=300 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The Setup</h2>



<p class="wp-block-paragraph">Main repo:</p>



<ul class="wp-block-list">
<li><a href="https://github.com/elbruno/ElBruno.NetAgent">https://github.com/elbruno/ElBruno.NetAgent</a></li>
</ul>



<p class="wp-block-paragraph">Previous experiments:</p>



<ol class="wp-block-list">
<li>CPU-only local model: <a href="https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/">https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/</a></li>



<li>GPU local model: <a href="https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/">https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/</a></li>



<li>GPT-5-mini BYOK: <a href="https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/">https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/</a></li>
</ol>



<p class="wp-block-paragraph">This time the goal was simple:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Run the same ElBruno.NetAgent app-building experiment using GitHub Copilot CLI + SQUAD + Azure OpenAI GPT-5.5 BYOK and compare stabilization loops against GPT-5-mini.</p>
</blockquote>



<p class="wp-block-paragraph">The stack:</p>



<ul class="wp-block-list">
<li>GitHub Copilot CLI</li>



<li>SQUAD orchestration</li>



<li>Azure OpenAI GPT-5.5 BYOK</li>



<li>.NET + WPF</li>



<li>Windows tray app</li>



<li>Strict quality gates</li>



<li>Manual UX validation</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<figure class="wp-block-image size-large"><img loading="lazy" width="688" height="384" src="https://elbruno.com/wp-content/uploads/2026/05/gem02-small.png?w=688" alt="" class="wp-image-40723" srcset="https://elbruno.com/wp-content/uploads/2026/05/gem02-small.png 688w, https://elbruno.com/wp-content/uploads/2026/05/gem02-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/gem02-small.png?w=300 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<h2 class="wp-block-heading">Step 0: Define “Working” Before Writing Code</h2>



<p class="wp-block-paragraph">One of the biggest lessons from the GPT-5-mini run was that “build green” was nowhere near enough.</p>



<p class="wp-block-paragraph">So before writing any product code, the experiment started with a dedicated Step 0 phase:</p>



<ul class="wp-block-list">
<li>Normalize SQUAD routing to GPT-5.5</li>



<li>Define team rules</li>



<li>Define safety guardrails</li>



<li>Define quality gates</li>



<li>Define manual UX validation</li>



<li>Prevent uncontrolled multi-agent fan-out</li>
</ul>



<p class="wp-block-paragraph">This turned out to be one of the smartest decisions in the whole experiment.</p>



<p class="wp-block-paragraph">The model was better, but the stronger engineering structure mattered even more.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The Phase-Based Build</h2>



<p class="wp-block-paragraph">Instead of asking the model to “build the app,” the project was split into very strict phases:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Phase</th><th class="has-text-align-left" data-align="left">Goal</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left">Phase 1</td><td class="has-text-align-left" data-align="left">Minimal .NET solution skeleton</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 2</td><td class="has-text-align-left" data-align="left">Core options, services, DI, safe defaults</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 3</td><td class="has-text-align-left" data-align="left">WPF shell and smoke-testable windows</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 4</td><td class="has-text-align-left" data-align="left">Editable Settings behavior</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 5</td><td class="has-text-align-left" data-align="left">Read-only Network Selector and dry-run logic</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 6</td><td class="has-text-align-left" data-align="left">Tray shell, Help/About, clean exit</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 7</td><td class="has-text-align-left" data-align="left">Status view and product polish</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 8</td><td class="has-text-align-left" data-align="left">Final automated validation</td></tr><tr><td class="has-text-align-left" data-align="left">Phase 9</td><td class="has-text-align-left" data-align="left">Manual UX bug fixing</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">This worked much better than broad “vibe coding.”</p>



<p class="wp-block-paragraph">GPT-5.5 was noticeably better at respecting:</p>



<ul class="wp-block-list">
<li>phase boundaries</li>



<li>“do not implement” instructions</li>



<li>safety rules</li>



<li>dry-run requirements</li>



<li>deterministic testing constraints</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Economy Mode Was the Real Unlock</h2>



<p class="wp-block-paragraph">Early SQUAD runs were expensive and noisy.</p>



<p class="wp-block-paragraph">Multiple agents reading large repo context repeatedly created a huge token tax.</p>



<p class="wp-block-paragraph">The biggest improvement came after switching to what I started calling “economy mode”:</p>



<ul class="wp-block-list">
<li>one implementation agent</li>



<li>one reviewer</li>



<li>no Scribe unless needed</li>



<li>no doc rewrites</li>



<li>no template rewrites</li>



<li>read only files needed for the phase</li>



<li>small targeted edits</li>



<li>stop after the phase</li>
</ul>



<p class="wp-block-paragraph">This dramatically reduced chaos and stabilization loops.</p>



<p class="wp-block-paragraph">Ironically, the better the model became, the more important project-management-style prompting became.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<figure class="wp-block-image size-large"><img loading="lazy" width="688" height="384" src="https://elbruno.com/wp-content/uploads/2026/05/gem06-small.png?w=688" alt="" class="wp-image-40718" srcset="https://elbruno.com/wp-content/uploads/2026/05/gem06-small.png 688w, https://elbruno.com/wp-content/uploads/2026/05/gem06-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/gem06-small.png?w=300 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<h2 class="wp-block-heading">The Cost Checkpoint</h2>



<p class="wp-block-paragraph">By the time the app reached final automated validation:</p>



<ul class="wp-block-list">
<li>~15.5M tokens consumed</li>



<li>400+ requests</li>



<li>~$30+ estimated Azure OpenAI cost</li>
</ul>



<p class="wp-block-paragraph">Most of the usage was input/context tokens, not output tokens.</p>



<p class="wp-block-paragraph">That’s an important lesson:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">SQUAD adds value, but every agent carries context overhead.</p>
</blockquote>



<p class="wp-block-paragraph">GPT-5.5 BYOK is powerful, but it is definitely not “free vibe coding.”</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The Quality Gates Worked… Until They Didn’t</h2>



<p class="wp-block-paragraph">The automated gate looked solid:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet clean .\ElBruno.NetAgent.sln</div><div class="cm-line">dotnet build .\ElBruno.NetAgent.sln -c Release</div><div class="cm-line">dotnet test .\ElBruno.NetAgent.sln -c Release --no-build --settings disable-parallel.runsettings</div><div class="cm-line">dotnet run --project src\ElBruno.NetAgent -- --smoke-test</div><div class="cm-line">dotnet run --project src\ElBruno.NetAgent -- --smoke-test-exit</div><div class="cm-line">dotnet run --project src\ElBruno.NetAgent -- --smoke-test-settings-save</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And honestly, GPT-5.5 handled this surprisingly well.</p>



<p class="wp-block-paragraph">The project reached:</p>



<ul class="wp-block-list">
<li>39+ passing tests</li>



<li>fast deterministic runs</li>



<li>smoke-test validation</li>



<li>WPF window construction validation</li>



<li>tray exit validation</li>



<li>forbidden-command safety scans</li>
</ul>



<p class="wp-block-paragraph">But then manual UX validation found a real bug.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The Manual UX Bug That Mattered</h2>



<p class="wp-block-paragraph">Manual testing showed:</p>



<ul class="wp-block-list">
<li>tray menu looked OK</li>



<li>Help worked</li>



<li>Exit worked</li>



<li>Settings UI looked rough</li>



<li>Save did not persist Auto Mode</li>
</ul>



<p class="wp-block-paragraph">At first this was confusing because:</p>



<ul class="wp-block-list">
<li>tests were green</li>



<li>smoke tests were green</li>



<li>SettingsViewModel tests passed</li>
</ul>



<p class="wp-block-paragraph">The problem was deeper: the tests validated the ViewModel, but not the real user path.</p>



<p class="wp-block-paragraph">The real failure path was:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">UI checkbox</div><div class="cm-line">→ Save and Close button</div><div class="cm-line">→ persisted config file</div><div class="cm-line">→ reload</div><div class="cm-line">→ UI state</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The fix was not “more tests.”</p>



<p class="wp-block-paragraph">The fix was:</p>



<ul class="wp-block-list">
<li>testing the actual WPF interaction path</li>



<li>validating persisted config values</li>



<li>validating reload behavior</li>



<li>validating the same SaveAndCloseAsync() path used by the real button</li>
</ul>



<p class="wp-block-paragraph">That was probably the most important lesson of the entire experiment.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The right test is the one that fails for the same reason your user fails.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What GPT-5.5 Did Better Than GPT-5-mini</h2>



<p class="wp-block-paragraph">GPT-5.5 was clearly better at:</p>



<ul class="wp-block-list">
<li>phase discipline</li>



<li>respecting safety boundaries</li>



<li>avoiding repo-wide chaos</li>



<li>using abstractions/fakes</li>



<li>isolating WPF/tray behavior</li>



<li>deterministic testing patterns</li>



<li>targeted fixes</li>



<li>reviewer coordination</li>
</ul>



<p class="wp-block-paragraph">The tray + exit phase was especially interesting.</p>



<p class="wp-block-paragraph">GPT-5-mini had previously struggled around:</p>



<ul class="wp-block-list">
<li>NotifyIcon behavior</li>



<li>WPF dispatcher behavior</li>



<li>tray shutdown</li>



<li>lingering processes</li>
</ul>



<p class="wp-block-paragraph">GPT-5.5 handled this much more cleanly once the prompts explicitly enforced:</p>



<ul class="wp-block-list">
<li>fake adapters</li>



<li>no native tray tests</li>



<li>no real dispatcher loops</li>



<li>deterministic test behavior</li>
</ul>



<p class="wp-block-paragraph">But stronger models still did not remove the need for:</p>



<ul class="wp-block-list">
<li>manual UX testing</li>



<li>safety reviews</li>



<li>persistence validation</li>



<li>engineering checkpoints</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What I Would Do Differently Next Time</h2>



<p class="wp-block-paragraph">A few practical lessons:</p>



<ul class="wp-block-list">
<li>Start with economy mode immediately</li>



<li>Use one implementer + one reviewer by default</li>



<li>Treat UI-path tests as mandatory</li>



<li>Validate persisted config files directly</li>



<li>Manual UX validation should happen earlier</li>



<li>Smoke tests should validate real user flows</li>



<li>Track token usage per phase</li>



<li>Keep phases narrow and explicit</li>
</ul>



<p class="wp-block-paragraph">Most importantly:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Define what “working” means before generating code.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<figure class="wp-block-image size-large"><img loading="lazy" width="688" height="384" src="https://elbruno.com/wp-content/uploads/2026/05/gem08-small.png?w=688" alt="" class="wp-image-40720" srcset="https://elbruno.com/wp-content/uploads/2026/05/gem08-small.png 688w, https://elbruno.com/wp-content/uploads/2026/05/gem08-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/gem08-small.png?w=300 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<h2 class="wp-block-heading">Final Thoughts</h2>



<p class="wp-block-paragraph">This experiment ended in a much better place than the GPT-5-mini run.</p>



<p class="wp-block-paragraph">The stabilization loops were smaller. The orchestration was cleaner. The engineering flow was more controlled.</p>



<p class="wp-block-paragraph">But the biggest lesson did not change.</p>



<p class="wp-block-paragraph">The hard part was never generating code.</p>



<p class="wp-block-paragraph">The hard part was:</p>



<ul class="wp-block-list">
<li>defining quality</li>



<li>validating behavior</li>



<li>testing real user flows</li>



<li>controlling orchestration</li>



<li>knowing when “green” was lying</li>
</ul>



<p class="wp-block-paragraph">GPT-5.5 made the engineering loop better.</p>



<p class="wp-block-paragraph">It did not eliminate the need for engineering discipline.</p>



<p class="wp-block-paragraph">And that is probably the most important AI-assisted development lesson I’ve learned so far:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Today, the code is cheap.<br />The decisions, validation, and engineering discipline are still expensive.</p>
</blockquote>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/14/github-copilot-cli-squad-gpt-5-5-byok-better-engineering-same-hard-truth/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40713</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png">
			<media:title type="html">ChatGPT Image May 14, 2026, 09_11_31 AM-small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-14-2026-09_11_31-am-small.png?w=836"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/img01-small.png?w=688"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gem02-small.png?w=688"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gem06-small.png?w=688"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gem08-small.png?w=688"/>
	</item>
		<item>
		<title>GitHub Copilot CLI + GPT-5-mini BYOK: The Code Was Cheap, the Quality Gates Were Expensive</title>
		<link>https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/</link>
					<comments>https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 11 May 2026 13:38:01 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[BYOK]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub Copilot CLI]]></category>
		<category><![CDATA[gpt-5-mini]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[technology]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40317</guid>

					<description><![CDATA[The first screenshot shows GitHub Copilot CLI ready with the SQUAD agent and GPT-5-mini selected. Then SQUAD started spawning background agents.]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="688" height="384" src="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png?w=688" alt="" class="wp-image-40328" srcset="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png 688w, https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png?w=300 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<div class="wp-block-jetpack-markdown"><h2>TL;DR</h2>
<p>This is part 3 of my GitHub Copilot CLI + BYOK experiment series.</p>
<p>After testing local models on CPU and GPU, I moved the same experiment to Azure OpenAI using GPT-5-mini through BYOK. Same repo, same goal, same safety constraints — but this time with a cloud model that made SQUAD practical again.</p>
<p>And that was the first big win.</p>
<p>With local models, the workflow was possible but fragile. With GPT-5-mini, GitHub Copilot CLI could coordinate SQUAD agents, run background tasks, create commits, fix issues, and move the app forward in a way that finally felt like real agent orchestration.</p>
<p>The result: ElBruno.NetAgent became a working Windows tray app for safe, dry-run network selection assistance.</p>
<p>But the real lesson was not “GPT-5-mini can generate code.”</p>
<p>The real lesson was this:</p>
<blockquote>
<p>The code was cheap. The quality gates were expensive.</p>
</blockquote>
<p>Build green was not enough. Tests green was not enough. Even smoke-test green was not enough at first.</p>
<p>We had to keep expanding the definition of “working” until it included runtime startup, dependency injection, WPF/XAML loading, tray lifecycle, fast deterministic tests, editable settings, save without freezing, and manual UX validation.</p>
</div>



<div class="wp-block-jetpack-markdown"><h1>The whole GPT-5-mini story</h1>
<h2>The experiment so far</h2>
<p>This is part 3 of the experiment series.</p>
<p>In the previous posts, I tested GitHub Copilot CLI with local models:</p>
<ul>
<li>CPU-only local model experiment: <a href="https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/">https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/</a></li>
<li>GPU local model experiment: <a href="https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/">https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/</a></li>
</ul>
<p>The repo for this experiment is here:</p>
<p><a href="https://github.com/elbruno/ElBruno.NetAgent" rel="nofollow">https://github.com/elbruno/ElBruno.NetAgent</a></p>
<p>And the GPT-5-mini work happened in this PR:</p>
<p><a href="https://github.com/elbruno/ElBruno.NetAgent/pull/3" rel="nofollow">https://github.com/elbruno/ElBruno.NetAgent/pull/3</a></p>
<p>The full series is about testing what can and cannot be done with GitHub Copilot CLI, SQUAD, and BYOK across different model setups:</p>
<ul>
<li>local models on CPU</li>
<li>local models on GPU</li>
<li>cloud model with GPT-5-mini</li>
<li>cloud model with GPT-5.5</li>
</ul>
<p>The final GPT-5.5 experiment is next. But before jumping there, this GPT-5-mini run deserves its own write-up, because it showed something very interesting.</p>
<p>The model was good enough to generate a lot of code.</p>
<p>But the hard part was not generating code.</p>
<p>The hard part was defining what “working” actually means.</p>
<h2>The setup</h2>
<p>The goal was to start from a clean repo and build a real app, not just a toy sample.</p>
<p>The app is ElBruno.NetAgent: a Windows tray app that helps with safe, dry-run network selection assistance.</p>
<p>The important phrase here is <strong>dry-run</strong>.</p>
<p>This app must not change Windows network settings. It must not enable or disable adapters. It must not run <code>netsh</code>, <code>Set-NetAdapter</code>, <code>Enable-NetAdapter</code>, <code>Disable-NetAdapter</code>, or anything similar.</p>
<p>The safety defaults were clear from the beginning:</p>
<ul>
<li><code>DryRunMode=true</code></li>
<li><code>AutoModeEnabled=false</code></li>
<li>no real network mutation</li>
<li>no admin/elevation requirement</li>
<li>no secrets</li>
<li>no NuGet publish unless explicitly requested</li>
</ul>
<p>For the GitHub Copilot CLI setup, the important part was using Azure OpenAI through BYOK and selecting GPT-5-mini.</p>
<p>Example shape of the setup:</p>
<pre><code class="language-powershell">$env:COPILOT_PROVIDER_TYPE=&quot;azure&quot;
$env:COPILOT_PROVIDER_BASE_URL=&quot;https://&lt;resource&gt;.openai.azure.com/openai/deployments/&lt;deployment&gt;&quot;
$env:COPILOT_PROVIDER_API_KEY=&quot;&lt;key&gt;&quot;
$env:COPILOT_PROVIDER_WIRE_API=&quot;responses&quot;
$env:COPILOT_MODEL=&quot;gpt-5-mini&quot;

$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS=&quot;32768&quot;
$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS=&quot;1536&quot;

copilot --agent squad --max-autopilot-continues 6
</code></pre>
<p>No keys in the repo, no secrets in the post, obviously.</p>
<p>The branch used for this run was:</p>
<pre><code class="language-text">gpt5miniGeneratedApp
</code></pre>
</div>



<div class="wp-block-jetpack-markdown"><h2>The first big difference: SQUAD worked</h2>
<p>This was the first major difference compared with the local model experiments.</p>
<p>With local models, SQUAD was either not practical, too slow, or fragile. The flow needed very small prompts, tight phases, and a lot of human checkpointing.</p>
<p>With GPT-5-mini BYOK, background agents started working.</p>
<p>That changed the whole experience.</p>
</div>



<figure class="wp-block-image size-large"><img loading="lazy" width="756" height="361" src="https://elbruno.com/wp-content/uploads/2026/05/image-1.png?w=756" alt="" class="wp-image-40321" srcset="https://elbruno.com/wp-content/uploads/2026/05/image-1.png 756w, https://elbruno.com/wp-content/uploads/2026/05/image-1.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/image-1.png?w=300 300w" sizes="(max-width: 756px) 100vw, 756px" /></figure>



<p class="wp-block-paragraph">The first screenshot shows GitHub Copilot CLI ready with the SQUAD agent and GPT-5-mini selected.</p>



<p class="wp-block-paragraph">Then SQUAD started spawning background agents.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="759" height="366" src="https://elbruno.com/wp-content/uploads/2026/05/image-2.png?w=759" alt="" class="wp-image-40322" srcset="https://elbruno.com/wp-content/uploads/2026/05/image-2.png 759w, https://elbruno.com/wp-content/uploads/2026/05/image-2.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/image-2.png?w=300 300w" sizes="(max-width: 759px) 100vw, 759px" /></figure>



<div class="wp-block-jetpack-markdown"><p>This is where the experiment started to feel different.</p>
<p>With local models, I was mostly trying to keep the workflow alive.</p>
<p>With GPT-5-mini, I could ask SQUAD to coordinate work, run agents in the background, and move from phase to phase.</p>
<p>It finally felt like agent orchestration instead of prompt babysitting.</p>
<h2>What the app became</h2>
<p>At the end of the GPT-5-mini run, ElBruno.NetAgent became a working Windows tray app.</p>
<p>The app can:</p>
<ul>
<li>start from <code>dotnet run --project src/ElBruno.NetAgent</code></li>
<li>show a tray icon</li>
<li>open a Status window</li>
<li>open a Network Selector window</li>
<li>open an editable Settings window</li>
<li>preview the best network switch in dry-run mode</li>
<li>expose a Help/About area with the GitHub repo link</li>
<li>save settings without freezing</li>
<li>exit cleanly and return the PowerShell prompt</li>
</ul>
</div>



<figure class="wp-block-image size-large"><img loading="lazy" width="711" height="452" src="https://elbruno.com/wp-content/uploads/2026/05/image-3.png?w=711" alt="" class="wp-image-40324" srcset="https://elbruno.com/wp-content/uploads/2026/05/image-3.png 711w, https://elbruno.com/wp-content/uploads/2026/05/image-3.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/image-3.png?w=300 300w" sizes="(max-width: 711px) 100vw, 711px" /></figure>



<div class="wp-block-jetpack-markdown"><p>The Network Selector shows detected interfaces, their kind, status, and quality information. It is still intentionally safe: no real adapter changes are performed.</p>
<p>That is important.</p>
<p>The app is not a “change my network settings automatically” app. It is a safe assistant that can inspect, score, recommend, and dry-run.</p>
<h2>Where things broke</h2>
<p>This is where the experiment became really valuable.</p>
<p>GPT-5-mini + SQUAD generated a lot of useful code, but the app did not become “working” in a single pass.</p>
<p>Every time we improved the quality gate, we found a new kind of bug.</p>
<p>Here are the most important ones.</p>
<table>
<thead>
<tr>
<th>What looked green</th>
<th>What was actually broken</th>
</tr>
</thead>
<tbody>
<tr>
<td>Build passed</td>
<td>The app failed at startup because of a DI registration issue</td>
</tr>
<tr>
<td>Tests passed</td>
<td>Runtime service resolution still failed</td>
</tr>
<tr>
<td>Smoke-test passed</td>
<td>Settings crashed with a XAML parse error</td>
</tr>
<tr>
<td>App launched</td>
<td>Exit logged shutdown messages but the process stayed alive</td>
</tr>
<tr>
<td>Settings opened</td>
<td>Settings was read-only</td>
</tr>
<tr>
<td>Settings became editable</td>
<td>Save froze the app</td>
</tr>
<tr>
<td>Tests were added for tray behavior</td>
<td>Tests hung for more than 25 minutes</td>
</tr>
</tbody>
</table>
<p>That table is basically the whole lesson.</p>
<p>Build green was not enough.</p>
<p>Tests green was not enough.</p>
<p>Even the first smoke-test was not enough.</p>
<p>Every time we made the definition of “working” more realistic, we found the next missing piece.</p>
<h2>The DI problem</h2>
<p>One of the first runtime issues was dependency injection.</p>
<p>The app built. Tests passed. But <code>dotnet run</code> failed because a service required <code>NetAgentOptions</code>, and the DI container did not know how to resolve it.</p>
<p>This was the first big reminder:</p>
<pre><code class="language-text">A build tells you the code compiles.
It does not tell you the app can start.
</code></pre>
<p>So the quality gate had to include a runtime startup validation.</p>
<p>That led to adding a smoke-test mode:</p>
<pre><code class="language-powershell">dotnet run --project src\ElBruno.NetAgent -- --smoke-test
</code></pre>
<p>At first, the smoke-test resolved core services and validated config.</p>
<p>Later, it had to do more.</p>
<h2>The XAML problem</h2>
<p>After fixing startup, the app launched and the tray menu opened.</p>
<p>Great, right?</p>
<p>Not yet.</p>
<p>Clicking Open Settings caused a runtime XAML parse exception.</p>
<p>The cause was a stray raw string inside a WPF UI container. Build passed. Tests passed. The first smoke-test passed. But the Settings window crashed when actually constructed by the app.</p>
<p>So the smoke-test evolved again.</p>
<p>It started validating that these windows could be constructed without being shown:</p>
<ul>
<li><code>SettingsWindow</code></li>
<li><code>StatusWindow</code></li>
<li><code>NetworkSelectorWindow</code></li>
</ul>
<p>That was another useful lesson:</p>
<pre><code class="language-text">A smoke-test that only resolves services is not enough for a desktop app.
It should catch XAML load errors too.
</code></pre>
<h2>The Exit problem</h2>
<p>Another fun one: the app would log that it was shutting down, but the process did not exit.</p>
<p>The logs looked promising:</p>
<pre><code class="language-text">Exit clicked.
Application is shutting down...
TrayIconService stopping.
</code></pre>
<p>But the PowerShell prompt did not return.</p>
<p>That meant something was still alive: a dispatcher, a tray resource, a hosted service, a foreground thread, or some combination of all the usual desktop app suspects.</p>
<p>The Exit path had to become explicit:</p>
<ul>
<li>hide/disable the tray icon</li>
<li>dispose the tray icon adapter</li>
<li>dispose context menu resources</li>
<li>close WPF windows</li>
<li>request WPF Application shutdown on the UI dispatcher</li>
<li>stop hosted services</li>
<li>make sure no foreground thread keeps the process alive</li>
</ul>
<p>Eventually, Exit worked and the app returned to PowerShell.</p>
<p>This became another gate:</p>
<pre><code class="language-powershell">dotnet run --project src\ElBruno.NetAgent -- --smoke-test-exit
</code></pre>
<h2>The test hang problem</h2>
<p>This was probably the most painful stabilization loop.</p>
<p>At one point, the test suite was still running after more than 25 minutes.</p>
<p>That is not a slow test.</p>
<p>That is a test hang.</p>
<p>The root cause was classic desktop-app testing pain: tests were touching native <code>System.Windows.Forms.NotifyIcon</code>, WPF Dispatcher behavior, and tray interactions as if they were normal unit tests.</p>
<p>They were not.</p>
<p>The fix was architectural:</p>
<ul>
<li>introduce <code>INotifyIconAdapter</code></li>
<li>use <code>TestNotifyIconAdapter</code> in unit tests</li>
<li>stop testing native NotifyIcon behavior in normal unit tests</li>
<li>mark real tray/WPF interaction tests as skipped/manual integration tests</li>
<li>use <code>disable-parallel.runsettings</code></li>
<li>keep the full unit test suite fast</li>
</ul>
<p>After that, the test suite went back to a few seconds.</p>
<p>The new rule became:</p>
<pre><code class="language-text">A quality gate is not valid unless tests finish quickly.
</code></pre>
<p>And another one:</p>
<pre><code class="language-text">If your tests need a real tray icon, they are probably not unit tests anymore.
</code></pre>
<h2>The Settings problem</h2>
<p>Once the app launched, Settings opened, and Exit worked, I found another issue.</p>
<p>Settings was read-only.</p>
<p>It showed useful information:</p>
<ul>
<li>Dry Run Mode</li>
<li>Auto Mode Enabled</li>
<li>Config File Path</li>
<li>App Data Folder</li>
</ul>
<p>But it did not allow editing the actual safe switching rules.</p>
<p>So Settings had to become a real configuration view.</p>
<p>The final Settings behavior included editable safe options like:</p>
<ul>
<li>DryRunMode</li>
<li>AutoModeEnabled</li>
<li>auto mode interval seconds</li>
<li>minimum quality score required to switch</li>
<li>minimum score improvement required</li>
<li>preferred interface names or patterns</li>
<li>excluded interface names or patterns</li>
<li>excluded interface kinds like Virtual, Loopback, VPN, Unknown</li>
<li>prefer USB tethering</li>
<li>allow Wi-Fi</li>
<li>allow Ethernet</li>
<li>ignore virtual adapters</li>
<li>ignore loopback adapters</li>
<li>ignore VPN adapters</li>
<li>ignore down or unknown-status adapters</li>
<li>Save</li>
<li>Reload</li>
<li>Reset to Safe Defaults</li>
</ul>
<p>Still no real network mutation.</p>
<p>Still dry-run first.</p>
<p>Still safe by default.</p>
<h2>The Save freeze problem</h2>
<p>Then came the final boss.</p>
<p>Settings was editable. The Save button existed. The values could be changed.</p>
<p>Then I clicked Save.</p>
<p>The app froze.</p>
<p>Build was green. Tests were green. Smoke-test was green. The Settings UI opened. Everything looked fine.</p>
<p>But a real user click exposed the bug.</p>
<p>The root cause was a classic UI-thread blocking issue.</p>
<p><code>RelayCommand.Execute</code> was synchronously waiting on async code using <code>GetAwaiter().GetResult()</code>.</p>
<p>That blocked the WPF UI thread during Save.</p>
<p>The fix was small but important:</p>
<ul>
<li>stop blocking the UI thread</li>
<li>let Save run asynchronously</li>
<li>add a smoke helper for Settings Save</li>
</ul>
<p>The new validation became:</p>
<pre><code class="language-powershell">dotnet run --project src\ElBruno.NetAgent -- --smoke-test-settings-save
</code></pre>
<p>That smoke-test verifies that Settings Save returns quickly and does not freeze.</p>
<p>This is exactly the kind of bug you only catch when you validate real app behavior, not just generated code.</p>
<h2>How the quality gate evolved</h2>
<p>At the beginning, the quality gate was simple.</p>
<pre><code class="language-powershell">dotnet build
dotnet test
</code></pre>
<p>That was not enough.</p>
<p>Then it became:</p>
<pre><code class="language-powershell">dotnet build -c Release
dotnet test -c Release --no-build
</code></pre>
<p>Still not enough.</p>
<p>Then we added runtime validation:</p>
<pre><code class="language-powershell">dotnet run --project src\ElBruno.NetAgent -- --smoke-test
</code></pre>
<p>Then we learned that <code>dotnet test --no-build</code> can lie after a failed build because it may run stale binaries.</p>
<p>So stabilization needed:</p>
<pre><code class="language-powershell">dotnet clean
dotnet build -c Release
dotnet test -c Release --no-build --settings disable-parallel.runsettings
</code></pre>
<p>Then we added targeted runtime checks:</p>
<pre><code class="language-powershell">dotnet run --project src\ElBruno.NetAgent -- --smoke-test
dotnet run --project src\ElBruno.NetAgent -- --smoke-test-exit
dotnet run --project src\ElBruno.NetAgent -- --smoke-test-settings-save
</code></pre>
<p>And finally, manual validation was mandatory:</p>
<ul>
<li>launch the app</li>
<li>open the tray menu</li>
<li>open Status</li>
<li>open Network Selector</li>
<li>open Settings</li>
<li>edit settings</li>
<li>save settings</li>
<li>confirm the UI does not freeze</li>
<li>close and reopen Settings</li>
<li>confirm values persisted</li>
<li>reset to safe defaults</li>
<li>preview best switch in dry-run mode</li>
<li>open Help/About</li>
<li>confirm the repo URL is shown</li>
<li>Exit</li>
<li>confirm PowerShell prompt returns</li>
</ul>
<p>This is the real quality gate.</p>
<p>Not because I like long checklists.</p>
<p>Because every missing item in that list caught a real bug.</p>
<h2>The cost</h2>
<p>Microsoft Foundry Monitor showed the following final-ish snapshot for this GPT-5-mini BYOK experiment:</p>
<ul>
<li>Total requests: 4.16K</li>
<li>Total token count: 104.27M</li>
<li>Input tokens: 103.05M</li>
<li>Output tokens: 1.22M</li>
<li>Estimated total cost: $6.81</li>
</ul>
<p>The dashboard estimate changed a little between refreshes, so I am treating this as a monitor estimate, not a final billing invoice.</p>
<p>But the order of magnitude is clear.</p>
<p>For roughly 104M tokens and about $6–$7 estimated cost, GPT-5-mini + SQUAD generated and stabilized a working Windows tray app.</p>
<p>That is kind of wild.</p>
<p>But here is the important part:</p>
<p>The Azure bill was not the blocker.</p>
<p>The hard part was teaching the agents what “working software” actually means.</p>
<h2>What worked well</h2>
<p>The biggest win was that GPT-5-mini made SQUAD practical again.</p>
<p>With the local models, the workflow needed very constrained prompts and constant babysitting. With GPT-5-mini BYOK, background agents could run, split work, report back, commit changes, and recover from some failures.</p>
<p>Other wins:</p>
<ul>
<li>scaffolding moved quickly</li>
<li>DI issues were fixable with targeted prompts</li>
<li>tests could be added and improved incrementally</li>
<li>docs and CI were updated along the way</li>
<li>the model handled refactors like <code>INotifyIconAdapter</code></li>
<li>smoke-test modes became part of the app</li>
<li>the app reached a real working state</li>
</ul>
<p>This was not perfect autonomy.</p>
<p>But it was much closer to a real agent workflow than the local model runs.</p>
<h2>What did not work well</h2>
<p>The model still needed strong human checkpoints.</p>
<p>Some examples:</p>
<ul>
<li>It trusted build/test success too much.</li>
<li>It sometimes committed before the final validation was really clean.</li>
<li>It created tests that were technically reasonable but bad for desktop app stability.</li>
<li>It mixed feature work and stabilization work when the prompt was too broad.</li>
<li>It needed repeated reminders to stop feature expansion and fix one bug only.</li>
<li>It sometimes solved the current failure but missed the product behavior.</li>
</ul>
<p>The biggest pattern was this:</p>
<p>When the task was too broad, the agents drifted.</p>
<p>When the task was narrow, they were useful.</p>
<p>That is a big lesson for the GPT-5.5 experiment.</p>
<h2>The lessons learned</h2>
<p>Here are the biggest lessons I am taking from this run.</p>
<h3>1. SQUAD became practical with cloud BYOK</h3>
<p>This was the biggest difference compared with local models.</p>
<p>Local CPU was possible but painful.</p>
<p>Local GPU was much better but still fragile.</p>
<p>GPT-5-mini BYOK made agent orchestration feel realistic.</p>
<h3>2. Build green is not enough</h3>
<p>The app compiled before it worked.</p>
<p>A lot.</p>
<h3>3. Tests green is not enough</h3>
<p>Tests can miss runtime DI, XAML, tray lifecycle, and real UI behavior.</p>
<p>Also, <code>dotnet test --no-build</code> can give false confidence after a failed build.</p>
<h3>4. Smoke-tests must evolve</h3>
<p>The smoke-test started as service resolution.</p>
<p>Then it had to include UI construction.</p>
<p>Then it needed exit validation.</p>
<p>Then Settings Save validation.</p>
<p>Every new smoke-test came from a real bug.</p>
<h3>5. Desktop apps need manual UX validation</h3>
<p>This is not optional.</p>
<p>A tray app can pass build, tests, and smoke-test, and still fail when a human clicks Save.</p>
<h3>6. Native tray tests are not unit tests</h3>
<p>Testing <code>NotifyIcon</code>, WPF Dispatcher, and real tray behavior in normal unit tests caused long hangs.</p>
<p>Adapters and fakes are not “nice to have.”</p>
<p>They are required.</p>
<h3>7. The stabilization phase matters</h3>
<p>The app generation phase was impressive.</p>
<p>The stabilization phase was where the real engineering happened.</p>
<h3>8. The code was cheap. The decisions were expensive.</h3>
<p>This is the core lesson.</p>
<p>The model can generate code quickly.</p>
<p>But deciding what to validate, where to put guardrails, what tests count, what safety means, and when the app is actually working — that is where the value is.</p>
<h2>What this means for GPT-5.5</h2>
<p>The next test will use GPT-5.5.</p>
<p>But the goal is not simply:</p>
<pre><code class="language-text">Can GPT-5.5 write more code?
</code></pre>
<p>That is not interesting enough.</p>
<p>The real question is:</p>
<pre><code class="language-text">Can GPT-5.5 reduce the stabilization loops?
</code></pre>
<p>For the GPT-5.5 experiment, I will start with all the lessons from this run baked in from minute zero:</p>
<ul>
<li>normalize SQUAD model routing first</li>
<li>define product behavior first</li>
<li>define safety guardrails first</li>
<li>define quality gates first</li>
<li>require <code>--smoke-test</code></li>
<li>require <code>--smoke-test-exit</code></li>
<li>require <code>--smoke-test-settings-save</code></li>
<li>use <code>disable-parallel.runsettings</code></li>
<li>avoid native NotifyIcon/WPF Dispatcher in unit tests</li>
<li>keep tray/WPF tests as manual integration tests</li>
<li>require manual UX validation before calling the app done</li>
</ul>
<p>That should make the comparison much more interesting.</p>
<p>If GPT-5.5 is stronger, I do not only want to see better code.</p>
<p>I want to see fewer rescue loops.</p>
<p>Fewer runtime DI issues.</p>
<p>Fewer XAML mistakes.</p>
<p>Fewer test architecture problems.</p>
<p>Fewer human corrections.</p>
<p>That is the real test.</p>
</div>



<div class="wp-block-jetpack-markdown"><h2>Final thoughts</h2>
<p>This experiment convinced me of something important.</p>
<p>AI can generate a lot of code very quickly.</p>
<p>But working software is not the same as generated code.</p>
<p>Working software means startup validation, DI checks, XAML loading, fast deterministic tests, safe defaults, manual UX validation, and boring things like “Save does not freeze the app.”</p>
<p>That boring stuff matters.</p>
<p>Actually, that boring stuff is the work.</p>
<p>So yes, GPT-5-mini + SQUAD built the app.</p>
<p>But the quality gates made it usable.</p>
<p>The code was cheap.</p>
<p>The decisions were expensive.</p>
<p>Next stop: same experiment, same repo, same rules — but with GPT-5.5.</p>
</div>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/11/github-copilot-cli-gpt-5-mini-byok-the-code-was-cheap-the-quality-gates-were-expensive/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40317</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png">
			<media:title type="html">Gemini_Generated_Image_vpj2hyvpj2hyvpj2-small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_vpj2hyvpj2hyvpj2-small.png?w=688"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/image-1.png?w=756"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/image-2.png?w=759"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/image-3.png?w=711"/>
	</item>
		<item>
		<title>From Pet Projects to +20 Open Source NuGet Packages, Thanks to GitHub Copilot &#128640;</title>
		<link>https://elbruno.com/2026/05/10/from-pet-projects-to-20-open-source-nuget-packages-thanks-to-github-copilot-%f0%9f%9a%80/</link>
					<comments>https://elbruno.com/2026/05/10/from-pet-projects-to-20-open-source-nuget-packages-thanks-to-github-copilot-%f0%9f%9a%80/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Sun, 10 May 2026 12:10:31 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[NuGet Packages]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40311</guid>

					<description><![CDATA[Hi! Some of these started as small pet projects. IE: a quick helper for a demo, a tiny tool for a conference, a library to avoid repeating the same code again and again, or one of those “I’ll just build this in one evening” ideas that somehow becomes a real thing. And now, thanks to [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="836" height="471" src="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png?w=836" alt="" class="wp-image-40313" srcset="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png 836w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png?w=768 768w" sizes="(max-width: 836px) 100vw, 836px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Some of these started as small pet projects.</p>



<p class="wp-block-paragraph">IE: a quick helper for a demo, a tiny tool for a conference, a library to avoid repeating the same code again and again, or one of those “I’ll just build this in one evening” ideas that somehow becomes a real thing.</p>



<p class="wp-block-paragraph">And now, thanks to GitHub Copilot, many of these experiments are becoming open source, free NuGet packages that I hope are useful to everyone.</p>



<p class="wp-block-paragraph">Some are focused on AI. Some are focused on local models. Some help with embeddings, speech, QR codes, document processing, MCP tools, agents, and developer productivity.</p>



<p class="wp-block-paragraph">In other words: a beautiful collection of useful chaos.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Quick package index</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Versions are current as of the latest dashboard snapshot: 2026-05-10.<br />Dashboard repo: <a href="https://github.com/elbruno/nuget-repo-dashboard?utm_source=chatgpt.com">https://github.com/elbruno/nuget-repo-dashboard</a></p>
</blockquote>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Package</th><th>Version</th><th>NuGet</th><th>GitHub</th></tr></thead><tbody><tr><td>ElBruno.AI.Evaluation</td><td>0.5.2</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AI.Evaluation" rel="nofollow">https://www.nuget.org/packages/ElBruno.AI.Evaluation</a></a></td><td><a><a href="https://github.com/elbruno/elbruno-ai-evaluation" rel="nofollow">https://github.com/elbruno/elbruno-ai-evaluation</a></a></td></tr><tr><td>ElBruno.AI.Evaluation.Reporting</td><td>0.5.2</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AI.Evaluation.Reporting" rel="nofollow">https://www.nuget.org/packages/ElBruno.AI.Evaluation.Reporting</a></a></td><td><a><a href="https://github.com/elbruno/elbruno-ai-evaluation" rel="nofollow">https://github.com/elbruno/elbruno-ai-evaluation</a></a></td></tr><tr><td>ElBruno.AI.Evaluation.SyntheticData</td><td>0.5.2</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AI.Evaluation.SyntheticData" rel="nofollow">https://www.nuget.org/packages/ElBruno.AI.Evaluation.SyntheticData</a></a></td><td><a><a href="https://github.com/elbruno/elbruno-ai-evaluation" rel="nofollow">https://github.com/elbruno/elbruno-ai-evaluation</a></a></td></tr><tr><td>ElBruno.AI.Evaluation.Xunit</td><td>0.5.2</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AI.Evaluation.Xunit" rel="nofollow">https://www.nuget.org/packages/ElBruno.AI.Evaluation.Xunit</a></a></td><td><a><a href="https://github.com/elbruno/elbruno-ai-evaluation" rel="nofollow">https://github.com/elbruno/elbruno-ai-evaluation</a></a></td></tr><tr><td>ElBruno.AgentsOrchestration.Abstractions</td><td>0.5.5</td><td><a href="https://www.nuget.org/packages/ElBruno.AgentsOrchestration.Abstractions">https://www.nuget.org/packages/ElBruno.AgentsOrchestration.Abstractions</a></td><td><a href="https://github.com/elbruno/elbruno.agentsorchestration?utm_source=chatgpt.com">https://github.com/elbruno/elbruno.agentsorchestration</a></td></tr><tr><td>ElBruno.AgentsOrchestration.Orchestration</td><td>0.5.5</td><td><a href="https://www.nuget.org/packages/ElBruno.AgentsOrchestration.Orchestration">https://www.nuget.org/packages/ElBruno.AgentsOrchestration.Orchestration</a></td><td><a href="https://github.com/elbruno/elbruno.agentsorchestration?utm_source=chatgpt.com">https://github.com/elbruno/elbruno.agentsorchestration</a></td></tr><tr><td>ElBruno.AotMapper</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AotMapper" rel="nofollow">https://www.nuget.org/packages/ElBruno.AotMapper</a></a></td><td><a href="https://github.com/elbruno/ElBruno.AotMapper?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.AotMapper</a></td></tr><tr><td>ElBruno.AotMapper.AspNetCore</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AotMapper.AspNetCore" rel="nofollow">https://www.nuget.org/packages/ElBruno.AotMapper.AspNetCore</a></a></td><td><a href="https://github.com/elbruno/ElBruno.AotMapper?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.AotMapper</a></td></tr><tr><td>ElBruno.AotMapper.EntityFramework</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AotMapper.EntityFramework" rel="nofollow">https://www.nuget.org/packages/ElBruno.AotMapper.EntityFramework</a></a></td><td><a href="https://github.com/elbruno/ElBruno.AotMapper?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.AotMapper</a></td></tr><tr><td>ElBruno.AotMapper.Generator</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AotMapper.Generator" rel="nofollow">https://www.nuget.org/packages/ElBruno.AotMapper.Generator</a></a></td><td><a href="https://github.com/elbruno/ElBruno.AotMapper?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.AotMapper</a></td></tr><tr><td>ElBruno.AspireMonitor</td><td>1.5.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.AspireMonitor" rel="nofollow">https://www.nuget.org/packages/ElBruno.AspireMonitor</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.AspireMonitor" rel="nofollow">https://github.com/elbruno/ElBruno.AspireMonitor</a></a></td></tr><tr><td>ElBruno.BM25</td><td>0.5.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.BM25" rel="nofollow">https://www.nuget.org/packages/ElBruno.BM25</a></a></td><td><a><a href="https://github.com/ElBruno/ElBruno.BM25" rel="nofollow">https://github.com/ElBruno/ElBruno.BM25</a></a></td></tr><tr><td>ElBruno.ClockTray</td><td>1.0.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.ClockTray" rel="nofollow">https://www.nuget.org/packages/ElBruno.ClockTray</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.ClockTray" rel="nofollow">https://github.com/elbruno/ElBruno.ClockTray</a></a></td></tr><tr><td>elbruno.Extensions.AI.Skills.Core</td><td>—</td><td><a><a href="https://www.nuget.org/packages/elbruno.Extensions.AI.Skills.Core" rel="nofollow">https://www.nuget.org/packages/elbruno.Extensions.AI.Skills.Core</a></a></td><td><a><a href="https://github.com/elbruno/elbruno-agentskills" rel="nofollow">https://github.com/elbruno/elbruno-agentskills</a></a></td></tr><tr><td>elbruno.Extensions.AI.Skills.Cli</td><td>—</td><td><a><a href="https://www.nuget.org/packages/elbruno.Extensions.AI.Skills.Cli" rel="nofollow">https://www.nuget.org/packages/elbruno.Extensions.AI.Skills.Cli</a></a></td><td><a><a href="https://github.com/elbruno/elbruno-agentskills" rel="nofollow">https://github.com/elbruno/elbruno-agentskills</a></a></td></tr><tr><td>ElBruno.HuggingFace.Downloader</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.HuggingFace.Downloader" rel="nofollow">https://www.nuget.org/packages/ElBruno.HuggingFace.Downloader</a></a></td><td><a href="https://github.com/elbruno/ElBruno.HuggingFace.Downloader">https://github.com/elbruno/ElBruno.HuggingFace.Downloader</a></td></tr><tr><td>ElBruno.HuggingFace.Downloader.Cli</td><td>—</td><td><a><a href="https://www.nuget.org/packages/ElBruno.HuggingFace.Downloader.Cli" rel="nofollow">https://www.nuget.org/packages/ElBruno.HuggingFace.Downloader.Cli</a></a></td><td><a href="https://github.com/elbruno/ElBruno.HuggingFace.Downloader">https://github.com/elbruno/ElBruno.HuggingFace.Downloader</a></td></tr><tr><td>ElBruno.LocalEmbeddings</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.Harrier</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Harrier" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Harrier</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.ImageEmbeddings</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.ImageEmbeddings" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.ImageEmbeddings</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.ImageEmbeddings.Downloader</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.ImageEmbeddings.Downloader" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.ImageEmbeddings.Downloader</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.KernelMemory</td><td>1.4.6</td><td><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.KernelMemory">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.KernelMemory</a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.Npu</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Npu" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Npu</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.Npu.Intel</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Npu.Intel" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Npu.Intel</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.Npu.Qualcomm</td><td>1.4.6</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Npu.Qualcomm" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Npu.Qualcomm</a></a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalEmbeddings.VectorData</td><td>1.4.6</td><td><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.VectorData">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.VectorData</a></td><td><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></td></tr><tr><td>ElBruno.LocalLLMs</td><td>0.16.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalLLMs" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalLLMs</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.LocalLLMs" rel="nofollow">https://github.com/elbruno/ElBruno.LocalLLMs</a></a></td></tr><tr><td>ElBruno.LocalLLMs.BitNet</td><td>0.16.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalLLMs.BitNet" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalLLMs.BitNet</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.LocalLLMs" rel="nofollow">https://github.com/elbruno/ElBruno.LocalLLMs</a></a></td></tr><tr><td>ElBruno.LocalLLMs.Rag</td><td>0.16.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.LocalLLMs.Rag" rel="nofollow">https://www.nuget.org/packages/ElBruno.LocalLLMs.Rag</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.LocalLLMs" rel="nofollow">https://github.com/elbruno/ElBruno.LocalLLMs</a></a></td></tr><tr><td>ElBruno.MarkItDotNet</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.AI</td><td>0.6.0</td><td><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.AI">https://www.nuget.org/packages/ElBruno.MarkItDotNet.AI</a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.AzureSearch</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.AzureSearch" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.AzureSearch</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Chunking</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Chunking" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Chunking</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Citations</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Citations" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Citations</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Cli</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Cli" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Cli</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.CoreModel</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.CoreModel" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.CoreModel</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.DocumentIntelligence</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.DocumentIntelligence" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.DocumentIntelligence</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Excel</td><td>0.6.0</td><td><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Excel">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Excel</a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Metadata</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Metadata" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Metadata</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.PowerPoint</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.PowerPoint" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.PowerPoint</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Quality</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Quality" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Quality</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Sync</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Sync" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Sync</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.VectorData</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.VectorData" rel="nofollow">https://www.nuget.org/packages/ElBruno.MarkItDotNet.VectorData</a></a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.MarkItDotNet.Whisper</td><td>0.6.0</td><td><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet.Whisper">https://www.nuget.org/packages/ElBruno.MarkItDotNet.Whisper</a></td><td><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></td></tr><tr><td>ElBruno.ModelContextProtocol.MCPToolRouter</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.ModelContextProtocol.MCPToolRouter" rel="nofollow">https://www.nuget.org/packages/ElBruno.ModelContextProtocol.MCPToolRouter</a></a></td><td><a href="https://github.com/elbruno/ElBruno.ModelContextProtocol">https://github.com/elbruno/ElBruno.ModelContextProtocol</a></td></tr><tr><td>ElBruno.OllamaMonitor</td><td>0.7.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.OllamaMonitor" rel="nofollow">https://www.nuget.org/packages/ElBruno.OllamaMonitor</a></a></td><td><a href="https://github.com/elbruno/ElBruno.OllamaMonitor?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.OllamaMonitor</a></td></tr><tr><td>ElBruno.OllamaSharp.Extensions</td><td>1.0.2</td><td><a><a href="https://www.nuget.org/packages/ElBruno.OllamaSharp.Extensions" rel="nofollow">https://www.nuget.org/packages/ElBruno.OllamaSharp.Extensions</a></a></td><td><a href="https://github.com/elbruno/elbruno.OllamaSharp.Extensions">https://github.com/elbruno/elbruno.OllamaSharp.Extensions</a></td></tr><tr><td>ElBruno.PersonaPlex</td><td>0.6.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.PersonaPlex" rel="nofollow">https://www.nuget.org/packages/ElBruno.PersonaPlex</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.PersonaPlex" rel="nofollow">https://github.com/elbruno/ElBruno.PersonaPlex</a></a></td></tr><tr><td>ElBruno.QRCodeGenerator.Ascii</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Ascii" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Ascii</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a></td></tr><tr><td>ElBruno.QRCodeGenerator.CLI</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.CLI" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.CLI</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator.CLI" rel="nofollow">https://github.com/elbruno/ElBruno.QRCodeGenerator.CLI</a></a></td></tr><tr><td>ElBruno.QRCodeGenerator.Image</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Image" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Image</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a></td></tr><tr><td>ElBruno.QRCodeGenerator.Payloads</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Payloads" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Payloads</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a></td></tr><tr><td>ElBruno.QRCodeGenerator.Pdf</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Pdf" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Pdf</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a></td></tr><tr><td>ElBruno.QRCodeGenerator.Svg</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Svg" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Svg</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a></td></tr><tr><td>ElBruno.QRCodeGenerator.Tool</td><td>1.1.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Tool" rel="nofollow">https://www.nuget.org/packages/ElBruno.QRCodeGenerator.Tool</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a></td></tr><tr><td>ElBruno.QwenTTS</td><td>1.4.7</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QwenTTS" rel="nofollow">https://www.nuget.org/packages/ElBruno.QwenTTS</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QwenTTS">https://github.com/elbruno/ElBruno.QwenTTS</a></td></tr><tr><td>ElBruno.QwenTTS.VoiceCloning</td><td>1.4.7</td><td><a><a href="https://www.nuget.org/packages/ElBruno.QwenTTS.VoiceCloning" rel="nofollow">https://www.nuget.org/packages/ElBruno.QwenTTS.VoiceCloning</a></a></td><td><a href="https://github.com/elbruno/ElBruno.QwenTTS">https://github.com/elbruno/ElBruno.QwenTTS</a></td></tr><tr><td>ElBruno.Realtime</td><td>0.10.0</td><td><a href="https://www.nuget.org/packages/ElBruno.Realtime">https://www.nuget.org/packages/ElBruno.Realtime</a></td><td><a href="https://github.com/elbruno/ElBruno.Realtime?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.Realtime</a></td></tr><tr><td>ElBruno.Realtime.SileroVad</td><td>0.10.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.Realtime.SileroVad" rel="nofollow">https://www.nuget.org/packages/ElBruno.Realtime.SileroVad</a></a></td><td><a href="https://github.com/elbruno/ElBruno.Realtime?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.Realtime</a></td></tr><tr><td>ElBruno.Realtime.Whisper</td><td>0.10.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.Realtime.Whisper" rel="nofollow">https://www.nuget.org/packages/ElBruno.Realtime.Whisper</a></a></td><td><a href="https://github.com/elbruno/ElBruno.Realtime?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.Realtime</a></td></tr><tr><td>ElBruno.Reranking</td><td>0.5.1</td><td><a><a href="https://www.nuget.org/packages/ElBruno.Reranking" rel="nofollow">https://www.nuget.org/packages/ElBruno.Reranking</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.Reranking" rel="nofollow">https://github.com/elbruno/ElBruno.Reranking</a></a></td></tr><tr><td>ElBruno.Text2Image</td><td>1.2.11</td><td><a><a href="https://www.nuget.org/packages/ElBruno.Text2Image" rel="nofollow">https://www.nuget.org/packages/ElBruno.Text2Image</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></a></td></tr><tr><td>ElBruno.Text2Image.Cli</td><td>1.2.11</td><td><a><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli" rel="nofollow">https://www.nuget.org/packages/ElBruno.Text2Image.Cli</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></a></td></tr><tr><td>ElBruno.Text2Image.Cpu</td><td>1.2.11</td><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cpu">https://www.nuget.org/packages/ElBruno.Text2Image.Cpu</a></td><td><a><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></a></td></tr><tr><td>ElBruno.Text2Image.Cuda</td><td>1.2.11</td><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cuda">https://www.nuget.org/packages/ElBruno.Text2Image.Cuda</a></td><td><a><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></a></td></tr><tr><td>ElBruno.Text2Image.DirectML</td><td>1.2.11</td><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.DirectML">https://www.nuget.org/packages/ElBruno.Text2Image.DirectML</a></td><td><a><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></a></td></tr><tr><td>ElBruno.Text2Image.Foundry</td><td>1.2.11</td><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Foundry">https://www.nuget.org/packages/ElBruno.Text2Image.Foundry</a></td><td><a><a href="https://github.com/elbruno/ElBruno.Text2Image" rel="nofollow">https://github.com/elbruno/ElBruno.Text2Image</a></a></td></tr><tr><td>ElBruno.VibeVoiceTTS</td><td>0.5.0</td><td><a><a href="https://www.nuget.org/packages/ElBruno.VibeVoiceTTS" rel="nofollow">https://www.nuget.org/packages/ElBruno.VibeVoiceTTS</a></a></td><td><a href="https://github.com/elbruno/ElBruno.VibeVoiceTTS">https://github.com/elbruno/ElBruno.VibeVoiceTTS</a></td></tr><tr><td>ElBruno.Whisper</td><td>0.2.0</td><td><a href="https://www.nuget.org/packages/ElBruno.Whisper">https://www.nuget.org/packages/ElBruno.Whisper</a></td><td><a><a href="https://github.com/elbruno/ElBruno.Whisper" rel="nofollow">https://github.com/elbruno/ElBruno.Whisper</a></a></td></tr><tr><td>graphify-dotnet</td><td>0.6.1</td><td><a><a href="https://www.nuget.org/packages/graphify-dotnet" rel="nofollow">https://www.nuget.org/packages/graphify-dotnet</a></a></td><td><a href="https://github.com/elbruno/graphify-dotnet">https://github.com/elbruno/graphify-dotnet</a></td></tr><tr><td>MemPalace.Core</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Core" rel="nofollow">https://www.nuget.org/packages/MemPalace.Core</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.Ai</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Ai" rel="nofollow">https://www.nuget.org/packages/MemPalace.Ai</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.Agents</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Agents" rel="nofollow">https://www.nuget.org/packages/MemPalace.Agents</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.Backends.Sqlite</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Backends.Sqlite" rel="nofollow">https://www.nuget.org/packages/MemPalace.Backends.Sqlite</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.KnowledgeGraph</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.KnowledgeGraph" rel="nofollow">https://www.nuget.org/packages/MemPalace.KnowledgeGraph</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.Mcp</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Mcp" rel="nofollow">https://www.nuget.org/packages/MemPalace.Mcp</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.Mining</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Mining" rel="nofollow">https://www.nuget.org/packages/MemPalace.Mining</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>MemPalace.Search</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/MemPalace.Search" rel="nofollow">https://www.nuget.org/packages/MemPalace.Search</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>mempalacenet</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/mempalacenet" rel="nofollow">https://www.nuget.org/packages/mempalacenet</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr><tr><td>mempalacenet-bench</td><td>0.15.2</td><td><a><a href="https://www.nuget.org/packages/mempalacenet-bench" rel="nofollow">https://www.nuget.org/packages/mempalacenet-bench</a></a></td><td><a><a href="https://github.com/elbruno/ElBruno.MempalaceNet" rel="nofollow">https://github.com/elbruno/ElBruno.MempalaceNet</a></a></td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">AI evaluation: because demos are easy, validation is the hard part</h2>



<p class="wp-block-paragraph">The <code>ElBruno.AI.Evaluation</code> family is about bringing testing discipline to AI apps.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.AI.Evaluation</code></li>



<li><code>ElBruno.AI.Evaluation.Reporting</code></li>



<li><code>ElBruno.AI.Evaluation.SyntheticData</code></li>



<li><code>ElBruno.AI.Evaluation.Xunit</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/elbruno-ai-evaluation">https://github.com/elbruno/elbruno-ai-evaluation</a></p>



<p class="wp-block-paragraph">These packages help with evaluation workflows, reporting, synthetic test data, and xUnit integration. The idea is simple: AI applications should not rely only on “it worked once in my demo.”</p>



<p class="wp-block-paragraph">They need repeatable checks.</p>



<p class="wp-block-paragraph">They need quality gates.</p>



<p class="wp-block-paragraph">They need tests that can run again tomorrow, when the model, prompt, data, or weather in the cloud changes.</p>



<p class="wp-block-paragraph">Because yes, AI apps are fun. But “trust me bro, the prompt is good” is not a testing strategy.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Agents and orchestration</h2>



<p class="wp-block-paragraph">The agents orchestration packages are focused on coordinating multiple AI agents through a lightweight workflow.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.AgentsOrchestration.Abstractions</code></li>



<li><code>ElBruno.AgentsOrchestration.Orchestration</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/elbruno.agentsorchestration">https://github.com/elbruno/elbruno.agentsorchestration</a></p>



<p class="wp-block-paragraph">This is useful when you want agents to do more than just reply to a prompt. The orchestration repo describes a 6-step pipeline:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Plan → Parse → Execute → Verify → Review → Report</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That is a nice mental model for agent-based workflows. It also fits very well with GitHub Copilot, SQUAD-style automation, and repo-based development experiments.</p>



<p class="wp-block-paragraph">Because once you have more than one agent, you need orchestration.</p>



<p class="wp-block-paragraph">Otherwise, congratulations, you invented a very expensive group chat.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">AOT-friendly mapping for .NET</h2>



<p class="wp-block-paragraph">The <code>ElBruno.AotMapper</code> family is focused on compile-time DTO mapping.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.AotMapper</code></li>



<li><code>ElBruno.AotMapper.AspNetCore</code></li>



<li><code>ElBruno.AotMapper.EntityFramework</code></li>



<li><code>ElBruno.AotMapper.Generator</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.AotMapper">https://github.com/elbruno/ElBruno.AotMapper</a></p>



<p class="wp-block-paragraph">This project is especially interesting for modern .NET workloads because it avoids runtime reflection and generates mapping code at compile time.</p>



<p class="wp-block-paragraph">That makes it useful for:</p>



<ul class="wp-block-list">
<li>NativeAOT apps</li>



<li>trimmed applications</li>



<li>cloud-native workloads</li>



<li>serverless scenarios</li>



<li>places where startup time and predictability matter</li>
</ul>



<p class="wp-block-paragraph">In short: less runtime magic, more generated code that you can actually inspect.</p>



<p class="wp-block-paragraph">And sometimes that is exactly what you want.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Local embeddings</h2>



<p class="wp-block-paragraph">The <code>ElBruno.LocalEmbeddings</code> family is one of the most useful groups for AI developers building RAG, semantic search, local-first AI apps, or privacy-aware demos.</p>



<p class="wp-block-paragraph">Packages include:</p>



<ul class="wp-block-list">
<li><code>ElBruno.LocalEmbeddings</code></li>



<li><code>ElBruno.LocalEmbeddings.Harrier</code></li>



<li><code>ElBruno.LocalEmbeddings.ImageEmbeddings</code></li>



<li><code>ElBruno.LocalEmbeddings.ImageEmbeddings.Downloader</code></li>



<li><code>ElBruno.LocalEmbeddings.KernelMemory</code></li>



<li><code>ElBruno.LocalEmbeddings.Npu</code></li>



<li><code>ElBruno.LocalEmbeddings.Npu.Intel</code></li>



<li><code>ElBruno.LocalEmbeddings.Npu.Qualcomm</code></li>



<li><code>ElBruno.LocalEmbeddings.VectorData</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/elbruno.localembeddings">https://github.com/elbruno/elbruno.localembeddings</a></p>



<p class="wp-block-paragraph">This family gives you local embedding generation in .NET, including support for text embeddings, image embeddings, vector data integrations, Kernel Memory, and NPU-specific packages.</p>



<p class="wp-block-paragraph">The NPU packages are especially cool because they connect directly with the AI PC story.</p>



<p class="wp-block-paragraph">Not every embedding call needs to cross the internet.</p>



<p class="wp-block-paragraph">Sometimes the best cloud call is the one you did not make.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Local LLMs</h2>



<p class="wp-block-paragraph">The <code>ElBruno.LocalLLMs</code> packages are focused on running and integrating local language models from .NET.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.LocalLLMs</code></li>



<li><code>ElBruno.LocalLLMs.BitNet</code></li>



<li><code>ElBruno.LocalLLMs.Rag</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.LocalLLMs">https://github.com/elbruno/ElBruno.LocalLLMs</a></p>



<p class="wp-block-paragraph">These are useful for local chat, local model experimentation, and RAG-style workflows.</p>



<p class="wp-block-paragraph">Cloud models are amazing. But sometimes you want the model running right there next to your code, your logs, your fan noise, and your questionable coffee.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">MarkItDotNet: from files to AI-ready Markdown</h2>



<p class="wp-block-paragraph">The <code>ElBruno.MarkItDotNet</code> family is probably one of the biggest and most useful areas in this package collection.</p>



<p class="wp-block-paragraph">Packages include:</p>



<ul class="wp-block-list">
<li><code>ElBruno.MarkItDotNet</code></li>



<li><code>ElBruno.MarkItDotNet.AI</code></li>



<li><code>ElBruno.MarkItDotNet.AzureSearch</code></li>



<li><code>ElBruno.MarkItDotNet.Chunking</code></li>



<li><code>ElBruno.MarkItDotNet.Citations</code></li>



<li><code>ElBruno.MarkItDotNet.Cli</code></li>



<li><code>ElBruno.MarkItDotNet.CoreModel</code></li>



<li><code>ElBruno.MarkItDotNet.DocumentIntelligence</code></li>



<li><code>ElBruno.MarkItDotNet.Excel</code></li>



<li><code>ElBruno.MarkItDotNet.Metadata</code></li>



<li><code>ElBruno.MarkItDotNet.PowerPoint</code></li>



<li><code>ElBruno.MarkItDotNet.Quality</code></li>



<li><code>ElBruno.MarkItDotNet.Sync</code></li>



<li><code>ElBruno.MarkItDotNet.VectorData</code></li>



<li><code>ElBruno.MarkItDotNet.Whisper</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></p>



<p class="wp-block-paragraph">This family is about converting, preparing, enriching, chunking, indexing, validating, and syncing content for AI workflows.</p>



<p class="wp-block-paragraph">In a typical RAG project, everyone wants the fancy chat UI.</p>



<p class="wp-block-paragraph">But before that, someone has to solve the real problem:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Can we clean and prepare the documents first?</p>
</blockquote>



<p class="wp-block-paragraph">That is where this package family fits.</p>



<p class="wp-block-paragraph">It helps turn files into AI-ready Markdown and supports scenarios like document conversion, chunking, metadata extraction, citations, Azure AI Search, Whisper transcription, and quality checks.</p>



<p class="wp-block-paragraph">Because every RAG project eventually becomes a document-cleanup project wearing an AI hat.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Text-to-image generation</h2>



<p class="wp-block-paragraph">The <code>ElBruno.Text2Image</code> packages provide a .NET-friendly way to work with image generation.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.Text2Image</code></li>



<li><code>ElBruno.Text2Image.Cli</code></li>



<li><code>ElBruno.Text2Image.Cpu</code></li>



<li><code>ElBruno.Text2Image.Cuda</code></li>



<li><code>ElBruno.Text2Image.DirectML</code></li>



<li><code>ElBruno.Text2Image.Foundry</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.Text2Image">https://github.com/elbruno/ElBruno.Text2Image</a></p>



<p class="wp-block-paragraph">The nice part here is the multi-backend design:</p>



<ul class="wp-block-list">
<li>CPU</li>



<li>CUDA</li>



<li>DirectML</li>



<li>Microsoft Foundry</li>



<li>CLI</li>
</ul>



<p class="wp-block-paragraph">That gives you flexibility depending on the machine, the demo, the budget, and how much your GPU is already crying.</p>



<p class="wp-block-paragraph">One API, multiple ways to make pixels appear like magic.</p>



<p class="wp-block-paragraph">Very expensive magic sometimes, but still magic.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Realtime audio, Whisper, and voice</h2>



<p class="wp-block-paragraph">This group is about speech, transcription, realtime audio, text-to-speech, and voice scenarios.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.Whisper</code></li>



<li><code>ElBruno.Realtime</code></li>



<li><code>ElBruno.Realtime.SileroVad</code></li>



<li><code>ElBruno.Realtime.Whisper</code></li>



<li><code>ElBruno.QwenTTS</code></li>



<li><code>ElBruno.QwenTTS.VoiceCloning</code></li>



<li><code>ElBruno.VibeVoiceTTS</code></li>



<li><code>ElBruno.PersonaPlex</code></li>
</ul>



<p class="wp-block-paragraph">Repos:</p>



<p class="wp-block-paragraph"><a><a href="https://github.com/elbruno/ElBruno.Whisper" rel="nofollow">https://github.com/elbruno/ElBruno.Whisper</a></a><br /><a href="https://github.com/elbruno/ElBruno.Realtime?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.Realtime</a><br /><a href="https://github.com/elbruno/ElBruno.QwenTTS">https://github.com/elbruno/ElBruno.QwenTTS</a><br /><a href="https://github.com/elbruno/ElBruno.VibeVoiceTTS">https://github.com/elbruno/ElBruno.VibeVoiceTTS</a><br /><a><a href="https://github.com/elbruno/ElBruno.PersonaPlex" rel="nofollow">https://github.com/elbruno/ElBruno.PersonaPlex</a></a></p>



<p class="wp-block-paragraph">This is useful for voice agents, transcription tools, accessibility scenarios, podcast workflows, and realtime conversational demos.</p>



<p class="wp-block-paragraph">Because sooner or later every AI demo becomes:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Can I talk to it?</p>
</blockquote>



<p class="wp-block-paragraph">And then, ten minutes later:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Why is my microphone still open?</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">QR code generation</h2>



<p class="wp-block-paragraph">The <code>ElBruno.QRCodeGenerator</code> family is a lightweight utility set for generating QR codes in different formats.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.QRCodeGenerator.Ascii</code></li>



<li><code>ElBruno.QRCodeGenerator.CLI</code></li>



<li><code>ElBruno.QRCodeGenerator.Image</code></li>



<li><code>ElBruno.QRCodeGenerator.Payloads</code></li>



<li><code>ElBruno.QRCodeGenerator.Pdf</code></li>



<li><code>ElBruno.QRCodeGenerator.Svg</code></li>



<li><code>ElBruno.QRCodeGenerator.Tool</code></li>
</ul>



<p class="wp-block-paragraph">Repos:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QRCodeGenerator</a><br /><a><a href="https://github.com/elbruno/ElBruno.QRCodeGenerator.CLI" rel="nofollow">https://github.com/elbruno/ElBruno.QRCodeGenerator.CLI</a></a></p>



<p class="wp-block-paragraph">This one is very practical: terminal output, images, SVG, PDF, payloads, and a global tool.</p>



<p class="wp-block-paragraph">Because every conference session eventually needs a QR code.</p>



<p class="wp-block-paragraph">Usually five minutes before going live.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">MCP and tool routing</h2>



<p class="wp-block-paragraph">The <code>ElBruno.ModelContextProtocol.MCPToolRouter</code> package focuses on smarter tool selection for Model Context Protocol scenarios.</p>



<p class="wp-block-paragraph">Package:</p>



<ul class="wp-block-list">
<li><code>ElBruno.ModelContextProtocol.MCPToolRouter</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.ModelContextProtocol">https://github.com/elbruno/ElBruno.ModelContextProtocol</a></p>



<p class="wp-block-paragraph">This is useful when working with agents that have access to many tools.</p>



<p class="wp-block-paragraph">Sending every possible tool to the model all the time is not always a good idea. It burns tokens, increases noise, and makes the model work harder than needed.</p>



<p class="wp-block-paragraph">Tool routing helps select the most relevant tools for the task.</p>



<p class="wp-block-paragraph">Agents are great.</p>



<p class="wp-block-paragraph">Agents with 97 tools in context are a token bonfire.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Search, ranking, and retrieval</h2>



<p class="wp-block-paragraph">These packages help with retrieval, ranking, and search scenarios.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.BM25</code></li>



<li><code>ElBruno.Reranking</code></li>



<li><code>graphify-dotnet</code></li>
</ul>



<p class="wp-block-paragraph">Repos:</p>



<p class="wp-block-paragraph"><a><a href="https://github.com/ElBruno/ElBruno.BM25" rel="nofollow">https://github.com/ElBruno/ElBruno.BM25</a></a><br /><a><a href="https://github.com/elbruno/ElBruno.Reranking" rel="nofollow">https://github.com/elbruno/ElBruno.Reranking</a></a><br /><a href="https://github.com/elbruno/graphify-dotnet">https://github.com/elbruno/graphify-dotnet</a></p>



<p class="wp-block-paragraph">This group fits nicely into RAG and knowledge discovery workflows.</p>



<p class="wp-block-paragraph">You need search.</p>



<p class="wp-block-paragraph">Then you need better search.</p>



<p class="wp-block-paragraph">Then you need reranking.</p>



<p class="wp-block-paragraph">Then you need graphs.</p>



<p class="wp-block-paragraph">Then you realize your “simple chatbot” is now a distributed knowledge system.</p>



<p class="wp-block-paragraph">Classic.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Developer desktop utilities</h2>



<p class="wp-block-paragraph">These are small tools that make developer life better.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.AspireMonitor</code></li>



<li><code>ElBruno.ClockTray</code></li>



<li><code>ElBruno.OllamaMonitor</code></li>
</ul>



<p class="wp-block-paragraph">Repos:</p>



<p class="wp-block-paragraph"><a><a href="https://github.com/elbruno/ElBruno.AspireMonitor" rel="nofollow">https://github.com/elbruno/ElBruno.AspireMonitor</a></a><br /><a><a href="https://github.com/elbruno/ElBruno.ClockTray" rel="nofollow">https://github.com/elbruno/ElBruno.ClockTray</a></a><br /><a href="https://github.com/elbruno/ElBruno.OllamaMonitor?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.OllamaMonitor</a></p>



<p class="wp-block-paragraph">These are the kind of tools that start with:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">I just need a tiny helper.</p>
</blockquote>



<p class="wp-block-paragraph">And then become useful enough to publish.</p>



<p class="wp-block-paragraph"><code>AspireMonitor</code> helps with Aspire monitoring.<br /><code>ClockTray</code> helps with Windows tray clock scenarios.<br /><code>OllamaMonitor</code> gives quick visibility into local Ollama runtime status.</p>



<p class="wp-block-paragraph">Tiny tools. Big quality-of-life improvement.</p>



<p class="wp-block-paragraph">The best kind of yak shaving.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Hugging Face helpers</h2>



<p class="wp-block-paragraph">The Hugging Face downloader packages help with downloading models and related assets from Hugging Face.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>ElBruno.HuggingFace.Downloader</code></li>



<li><code>ElBruno.HuggingFace.Downloader.Cli</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.HuggingFace.Downloader">https://github.com/elbruno/ElBruno.HuggingFace.Downloader</a></p>



<p class="wp-block-paragraph">This is useful across many of the local AI packages, because local AI usually starts with:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Great, now where do I get the model files?</p>
</blockquote>



<p class="wp-block-paragraph">And then:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Why is this model 4 GB?</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">OllamaSharp extensions</h2>



<p class="wp-block-paragraph">Package:</p>



<ul class="wp-block-list">
<li><code>ElBruno.OllamaSharp.Extensions</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/elbruno.OllamaSharp.Extensions">https://github.com/elbruno/elbruno.OllamaSharp.Extensions</a></p>



<p class="wp-block-paragraph">This package adds useful extensions around OllamaSharp, especially for scenarios where local LLM calls can take longer and timeout management becomes important.</p>



<p class="wp-block-paragraph">If you have ever waited for a local model to respond and wondered whether it was thinking, frozen, or silently judging your prompt, this one makes sense.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">MemPalace: memory for AI apps</h2>



<p class="wp-block-paragraph">The <code>MemPalace</code> family is focused on memory infrastructure for AI apps and agents.</p>



<p class="wp-block-paragraph">Packages:</p>



<ul class="wp-block-list">
<li><code>MemPalace.Core</code></li>



<li><code>MemPalace.Ai</code></li>



<li><code>MemPalace.Agents</code></li>



<li><code>MemPalace.Backends.Sqlite</code></li>



<li><code>MemPalace.KnowledgeGraph</code></li>



<li><code>MemPalace.Mcp</code></li>



<li><code>MemPalace.Mining</code></li>



<li><code>MemPalace.Search</code></li>



<li><code>mempalacenet</code></li>



<li><code>mempalacenet-bench</code></li>
</ul>



<p class="wp-block-paragraph">Repo:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.MempalaceNet">https://github.com/elbruno/ElBruno.MempalaceNet</a></p>



<p class="wp-block-paragraph">This is about local-first AI memory, storage, search, knowledge graphs, agents, MCP integration, mining, and benchmarking.</p>



<p class="wp-block-paragraph">Because agents without memory are just very confident goldfish.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Closing thoughts</h2>



<p class="wp-block-paragraph">Most of these packages started as small ideas, experiments, demos, or helper libraries.</p>



<p class="wp-block-paragraph">But this is one of the things I love about modern development with GitHub Copilot: it makes it easier to move from:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">This works on my machine</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">to:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">This is packaged, documented, published, open source, and maybe useful for someone else.</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Are all of these perfect?</p>



<p class="wp-block-paragraph">Of course not.</p>



<p class="wp-block-paragraph">Are they useful?</p>



<p class="wp-block-paragraph">I hope so.</p>



<p class="wp-block-paragraph">Are they free, open source, and ready for you to try, break, improve, fork, complain about, and maybe even use in a real project?</p>



<p class="wp-block-paragraph">Yes. That is the idea.</p>



<p class="wp-block-paragraph">And as always:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Today, code is cheap. The decisions are expensive.</p>
</blockquote>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/10/from-pet-projects-to-20-open-source-nuget-packages-thanks-to-github-copilot-%f0%9f%9a%80/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40311</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png">
			<media:title type="html">ChatGPT Image May 10, 2026, 08_03_42 AM-small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-10-2026-08_03_42-am-small.png?w=836"/>
	</item>
		<item>
		<title>Running GitHub Copilot CLI Offline with Local Models: GPU Edition</title>
		<link>https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/</link>
					<comments>https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 06 May 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40289</guid>

					<description><![CDATA[TL;DR After my first CPU-only experiment with GitHub Copilot CLI and local models, I wanted to try the same idea again, but this time with a GPU-powered setup. The first experiment proved that running GitHub Copilot CLI offline with local models was technically possible. This second experiment showed something more interesting: With the right model, [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<h2 class="wp-block-heading">TL;DR</h2>



<p class="wp-block-paragraph">After my first CPU-only experiment with GitHub Copilot CLI and local models, I wanted to try the same idea again, but this time with a GPU-powered setup.</p>



<p class="wp-block-paragraph">The first experiment proved that running GitHub Copilot CLI offline with local models was technically possible.</p>



<p class="wp-block-paragraph">This second experiment showed something more interesting:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">With the right model, GPU acceleration, small bounded phases, and strict human checkpoints, local agentic coding can become practical enough to move a real .NET project forward.</p>
</blockquote>



<p class="wp-block-paragraph">The setup:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">GitHub Copilot CLI<br />LM Studio<br />Qwen3.6 35B A3B<br />GPU acceleration<br />.NET 10<br />PowerShell<br />GitHub CLI<br />GitHub Actions</p>
</blockquote>



<p class="wp-block-paragraph">The project:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">ElBruno.NetAgent<br />A Windows-first .NET network utility, built with dry-run safety first.</p>
</blockquote>



<p class="wp-block-paragraph">Repository:</p>



<p class="wp-block-paragraph"><a href="https://github.com/brunoghcpft-oss/ElBruno.NetAgent">https://github.com/brunoghcpft-oss/ElBruno.NetAgent</a></p>



<p class="wp-block-paragraph">Previous CPU-only post:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-el-bruno wp-block-embed-el-bruno"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="oOOsFOzVpA"><a href="https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/">Running GitHub Copilot CLI Offline with Local Models: A CPU-Only Reality&nbsp;Check</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Running GitHub Copilot CLI Offline with Local Models: A CPU-Only Reality&nbsp;Check&#8221; &#8212; El Bruno" src="https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/embed/#?secret=Rzx7LeoKuj#?secret=oOOsFOzVpA" data-secret="oOOsFOzVpA" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p class="wp-block-paragraph">The short version:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Cloud models would have been much faster.<br />Local mode took more than a day.<br />But the local workflow actually worked.<br />And the lessons were worth it.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">From CPU-only reality check to GPU-assisted local agents</h2>



<p class="wp-block-paragraph">In the first experiment, I tested GitHub Copilot CLI offline using local models on a CPU-only environment.</p>



<p class="wp-block-paragraph">That test was useful, but also humbling.</p>



<p class="wp-block-paragraph">The model could reason, but it was slow. Broad prompts were painful. Asking something like:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">How does this repository work?</p>
</blockquote>



<p class="wp-block-paragraph">was not a good idea. The model had too much to process, tool calls became expensive, and long-running attempts could easily end in timeouts or partial answers.</p>



<p class="wp-block-paragraph">So the first big lesson was simple:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Offline local agentic coding is possible, but CPU-only is a reality check.</p>
</blockquote>



<p class="wp-block-paragraph">This time I wanted to answer a different question:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">What changes when we add GPU acceleration?</p>
</blockquote>



<p class="wp-block-paragraph">Not in a synthetic benchmark. Not in a “hello world” repo. But in a real .NET project with tests, CI, packaging, docs, safety constraints, and GitHub Actions.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The setup</h2>



<p class="wp-block-paragraph">The local provider was&nbsp;<strong>LM Studio</strong>, exposing an OpenAI-compatible endpoint.</p>



<p class="wp-block-paragraph">The model was:</p>



<p class="wp-block-paragraph">qwen/qwen3.6-35b-a3b</p>



<p class="wp-block-paragraph">LM Studio was configured with a large context window:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Context length: 262,144 tokens<br />GPU offload enabled<br />Unified KV cache enabled<br />Model kept in memory</p>
</blockquote>



<p class="wp-block-paragraph">For this GPU experiment, I used a <strong>Microsoft Dev Box</strong> with an <strong>NVIDIA Tesla T4 GPU with 16 GB of VRAM</strong>, an <strong>AMD EPYC 7V12 64-core processor</strong>, and <strong>110 GB of RAM</strong>. This was not a high-end consumer GPU workstation, but it was enough to make the local Copilot CLI + LM Studio + Qwen workflow practical. Compared with the CPU-only experiment, the difference was very noticeable, even though powerful cloud models would still be significantly faster.</p>



<p class="wp-block-paragraph">Copilot CLI was configured to use the local LM Studio endpoint:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">cd</span> <span class="tok-variableName">C:</span><span class="tok-punctuation">\</span><span class="tok-variableName">src</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_OFFLINE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;true&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_TYPE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;openai&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_BASE_URL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;http://localhost:1234/v1&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_MODEL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;qwen/qwen3.6-35b-a3b&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Depending on the task, I adjusted the context and output limits.</p>



<p class="wp-block-paragraph">For diagnostic tasks with logs:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;32768&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;1536&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">For large documentation or release-readiness tasks:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;131072&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;4096&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">For full repo audit or very large synthesis:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;196608&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;4096&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Or, when really needed:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;262144&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;4096&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">But I quickly learned not to use maximum context by default.</p>



<p class="wp-block-paragraph">More context is not always better.</p>



<p class="wp-block-paragraph">More context can also mean:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">slower responses<br />more token burn<br />more chance of runaway generation<br />more opportunity for the model to overthink<br />more tool-call noise</p>
</blockquote>



<p class="wp-block-paragraph">The short rule became:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Small context for precision. Large context for synthesis. Max context only when the task truly needs it.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Launching Copilot CLI offline</h2>



<p class="wp-block-paragraph">For most implementation phases, I launched Copilot CLI like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">copilot</span> <span class="tok-operator">--</span><span class="tok-variableName">no-remote</span> <span class="tok-operator">--</span><span class="tok-variableName">disable-builtin-mcps</span> <span class="tok-operator">--</span><span class="tok-variableName">stream</span> <span class="tok-variableName">on</span> <span class="tok-operator">--</span><span class="tok-variableName">max-autopilot-continues</span> <span class="tok-number">6</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">When file edits were needed, I enabled full tool permissions inside Copilot CLI:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">/allow-all on</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And verified with:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">/allow-all show</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This mattered a lot.</p>



<p class="wp-block-paragraph">Without&nbsp;<code>/allow-all on</code>, Copilot CLI could read files and run read-only commands, but file-edit tools were blocked with errors like:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Permission denied and could not request permission from user</p>
</blockquote>



<p class="wp-block-paragraph">After enabling&nbsp;<code>/allow-all on</code>, Copilot CLI was able to apply targeted changes.</p>



<p class="wp-block-paragraph">That became another useful rule:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Diagnosis mode:</p>



<ul class="wp-block-list">
<li>no yolo</li>



<li>smaller context</li>



<li>read files</li>



<li>read logs</li>



<li>analyze</li>



<li>stop</li>
</ul>



<p class="wp-block-paragraph">Implementation mode:</p>



<ul class="wp-block-list">
<li>/allow-all on</li>



<li>constrained prompt</li>



<li>explicitly listed files</li>



<li>low max-autopilot-continues</li>



<li>validate</li>



<li>stop</li>
</ul>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The project: ElBruno.NetAgent</h2>



<p class="wp-block-paragraph">The project used for the experiment was&nbsp;<strong>ElBruno.NetAgent</strong>:</p>



<p class="wp-block-paragraph"><a href="https://github.com/brunoghcpft-oss/ElBruno.NetAgent">https://github.com/brunoghcpft-oss/ElBruno.NetAgent</a></p>



<p class="wp-block-paragraph">The goal of the app is to become a Windows-first network utility that can monitor network interfaces, evaluate connection quality, and eventually help with network-switching decisions.</p>



<p class="wp-block-paragraph">The important part: during this experiment, the app stayed&nbsp;<strong>dry-run only</strong>.</p>



<p class="wp-block-paragraph">No real network mutation. No actual interface metric changes. No live switching. No NuGet publishing.</p>



<p class="wp-block-paragraph">This was intentional. Since I was letting Copilot CLI and a local model modify code, scripts, tests, docs, and CI, safety constraints were non-negotiable.</p>



<p class="wp-block-paragraph">The app had to stay in this mode:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Dry-run only<br />Live mode blocked by default<br />Unsafe config rejected<br />Audit logs for simulated actions<br />No real network mutation</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What the local model actually helped build</h2>



<p class="wp-block-paragraph">This was not a single prompt experiment.</p>



<p class="wp-block-paragraph">It was a sequence of bounded phases.</p>



<p class="wp-block-paragraph">Some of the validated phases included:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Phase 6: Network controller dry-run foundation<br />Phase 7: Audit log for simulated controller actions<br />Phase 8: Tray UI status + audit log viewer<br />Phase 9: Packaging validation<br />Phase 10: Safety guardrails<br />Phase 11: NuGet package icon with t2i<br />Phase 12: Local package smoke test<br />Phase 13: CI packaging validation<br />Phase 14: CI test isolation fix</p>
</blockquote>



<p class="wp-block-paragraph">By the end, the project had:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">.NET 10 project structure<br />WPF tray shell<br />configuration service<br />network inventory foundation<br />network quality monitor<br />decision engine<br />dry-run network controller<br />audit log service<br />UI status surface<br />NuGet metadata<br />package icon<br />local package smoke test<br />GitHub Actions CI validation<br />safety guardrails<br />125 passing tests</p>
</blockquote>



<p class="wp-block-paragraph">That is the part that made this experiment interesting.</p>



<p class="wp-block-paragraph">The local model was not just generating sample code. It was participating in a real workflow.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Model choice mattered</h2>



<p class="wp-block-paragraph">This experiment also reminded me that “local model” is not a single category.</p>



<p class="wp-block-paragraph">I originally started experimenting with&nbsp;<strong>Gemma 4</strong>, but eventually switched to&nbsp;<strong>Qwen3.6 35B A3B</strong>.</p>



<p class="wp-block-paragraph">In this setup, Qwen behaved better.</p>



<p class="wp-block-paragraph">It felt more disciplined for bounded tasks. It handled tool-assisted workflows better. It was also faster in the specific LM Studio + GPU configuration I was using.</p>



<p class="wp-block-paragraph">That does not mean Qwen is universally better than Gemma.</p>



<p class="wp-block-paragraph">It means that for this specific setup:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">GitHub Copilot CLI<br />LM Studio<br />large-context local model<br />.NET repo<br />PowerShell tools<br />GitHub CLI<br />bounded coding tasks</p>
</blockquote>



<p class="wp-block-paragraph">Qwen was the model that made the workflow practical.</p>



<p class="wp-block-paragraph">This is an important point: local agentic coding is not just about having “a local model.”</p>



<p class="wp-block-paragraph">The specific model matters. The runtime matters. The prompt matters. The tool orchestration matters.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Cloud would have been faster. That was not the point.</h2>



<p class="wp-block-paragraph">Let’s be honest.</p>



<p class="wp-block-paragraph">A powerful cloud model could probably have completed most of this much faster.</p>



<p class="wp-block-paragraph">My guess: with a strong cloud model, good repo context, and the same human guidance, many of these phases could probably be completed in one or two hours.</p>



<p class="wp-block-paragraph">The local GPU workflow took more than a day.</p>



<p class="wp-block-paragraph">There were restarts. There were prompt adjustments. There were permission issues. There were GitHub CLI authentication steps. There were runaway generations. There were context-tuning experiments. There were CI failures. There were moments where I had to stop the agent and take back control.</p>



<p class="wp-block-paragraph">So no, this was not faster than using a powerful cloud model.</p>



<p class="wp-block-paragraph">But speed was not the only variable I wanted to test.</p>



<p class="wp-block-paragraph">I wanted to understand:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Can Copilot CLI work with a local OpenAI-compatible provider?<br />Can a local model use tools well enough to move a real repo forward?<br />Can it diagnose GitHub Actions failures from logs?<br />Can it respect dry-run safety constraints?<br />Can it generate packaging assets?<br />Can it validate build, test, pack, and smoke-test flows?<br />How much human steering is required?<br />Where does the local workflow break?</p>
</blockquote>



<p class="wp-block-paragraph">The answer was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Yes, it can work. But it needs constraints, checkpoints, and patience.</p>
</blockquote>



<p class="wp-block-paragraph">Local models are not yet the fastest path for this workflow.</p>



<p class="wp-block-paragraph">But they are becoming a realistic path for controlled, private, offline-capable experimentation.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The token burn is real</h2>



<p class="wp-block-paragraph">One of the most useful screenshots from the experiment showed a real Copilot CLI run with something like:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Duration: 1h 30m<br />Input tokens: ~1.8M<br />Output tokens: ~13.5K<br />Reasoning tokens: ~2.2K</p>
</blockquote>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="348" src="https://elbruno.com/wp-content/uploads/2026/05/image.png?w=1024" alt="" class="wp-image-40296" srcset="https://elbruno.com/wp-content/uploads/2026/05/image.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/05/image.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/image.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/image.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/05/image.png 1114w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">This is a very important part of the story.</p>



<p class="wp-block-paragraph">Local does not mean free.</p>



<p class="wp-block-paragraph">It means the cost moves somewhere else:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">your GPU<br />your CPU<br />your memory<br />your time<br />your electricity<br />your patience<br />your prompt discipline</p>
</blockquote>



<p class="wp-block-paragraph">Or, in a more honest way:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The model does not charge per token, but your GPU, your laptop, and your coffee do have limits.</p>
</blockquote>



<p class="wp-block-paragraph">This changed how I structured the work.</p>



<p class="wp-block-paragraph">I stopped thinking in terms of “one big prompt.”</p>



<p class="wp-block-paragraph">Instead, I used phases.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Bounded phases were the real unlock</h2>



<p class="wp-block-paragraph">The most important lesson was not “use a GPU.”</p>



<p class="wp-block-paragraph">The most important lesson was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Small, bounded phases work. Huge open-ended prompts fail.</p>
</blockquote>



<p class="wp-block-paragraph">Good prompts looked like this:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Implement only this phase.<br />Modify only these files.<br />Do not use agents.<br />Do not use read_agent.<br />Do not use SQL.<br />Do not publish.<br />Do not mutate network state.<br />Run this exact validation command.<br />Stop after the summary.</p>
</blockquote>



<p class="wp-block-paragraph">Bad prompts looked like this:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Analyze the whole repo and improve everything.<br />Make it production ready.<br />Fix all warnings.<br />Validate everything.<br />Continue until done.</p>
</blockquote>



<p class="wp-block-paragraph">The local model could handle focused tasks much better than open-ended ones. A good implementation prompt looked like this:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Apply this exact minimal fix.</p>



<p class="wp-block-paragraph">Do not diagnose.<br />Do not use agents.<br />Do not use read_agent.<br />Do not use SQL.<br />Do not use todo tracking.<br />Do not edit CI YAML.<br />Do not modify any files except:</p>



<ul class="wp-block-list">
<li>src\ElBruno.NetAgent\Services\Configuration\ConfigurationService.cs</li>



<li>tests\ElBruno.NetAgent.Tests\SafetyGuardrailTests.cs</li>
</ul>



<p class="wp-block-paragraph">If any write/edit tool fails with &#8220;Permission denied and could not request permission from user&#8221;, stop immediately. Do not try alternate write methods.</p>



<p class="wp-block-paragraph">Required change 1:<br />In ConfigurationService.cs, add an optional constructor parameter:<br />string? configDirectoryOverride = null</p>



<p class="wp-block-paragraph">Default behavior must remain unchanged.<br />If configDirectoryOverride is null, continue using:<br />Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)<br />combined with ElBruno.NetAgent.</p>



<p class="wp-block-paragraph">If configDirectoryOverride is provided, use that directory as the configuration directory.</p>



<p class="wp-block-paragraph">Required change 2:<br />In SafetyGuardrailTests.cs, update only the test:<br />SafetyChain_DefaultConfig_AllowsNoLiveExecution</p>



<p class="wp-block-paragraph">Make this test use a unique temp directory:<br />Path.Combine(Path.GetTempPath(), &#8220;ElBruno.NetAgent.Test-&#8221; + Guid.NewGuid().ToString(&#8220;N&#8221;))</p>



<p class="wp-block-paragraph">Create the directory before using it.<br />Pass it to the ConfigurationService constructor.<br />Clean it up in finally if practical.</p>



<p class="wp-block-paragraph">Validation:<br />Run only:<br />dotnet test .\tests\ElBruno.NetAgent.Tests\ElBruno.NetAgent.Tests.csproj &#8211;configuration Release &#8211;verbosity minimal</p>



<p class="wp-block-paragraph">After validation, summarize:</p>



<ul class="wp-block-list">
<li>exact files changed</li>



<li>test result</li>



<li>whether I should commit and push</li>
</ul>



<p class="wp-block-paragraph">Stop after summary.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Tool-assisted diagnosis worked better than full autopilot</h2>



<p class="wp-block-paragraph">One of the best parts of the experiment happened when GitHub Actions failed.</p>



<p class="wp-block-paragraph">Local validation passed:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">dotnet build: passed<br />dotnet test: 125/125 passed<br />dotnet pack: passed<br />local package smoke test: passed</p>
</blockquote>



<p class="wp-block-paragraph">But GitHub Actions failed in CI.</p>



<p class="wp-block-paragraph">Instead of asking Copilot CLI to “fix CI,” I gave it a diagnostic-only prompt:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Goal:<br />Diagnose a failing GitHub Actions workflow using minimal tool-assisted analysis.</p>



<p class="wp-block-paragraph">Use tools only to gather the minimum data:</p>



<ol class="wp-block-list">
<li>Read the local workflow:<br />Get-Content ..github\workflows\ci.yml</li>



<li>Read the test project file:<br />Get-Content .\tests\ElBruno.NetAgent.Tests\ElBruno.NetAgent.Tests.csproj</li>



<li>Read the app project file:<br />Get-Content .\src\ElBruno.NetAgent\ElBruno.NetAgent.csproj</li>



<li>Fetch the failed GitHub Actions job log:<br />gh run view 25378546671 &#8211;job 74420179140 &#8211;log-failed</li>
</ol>



<p class="wp-block-paragraph">After gathering data, return:</p>



<ol class="wp-block-list">
<li>exact failing evidence from the CI log</li>



<li>likely root cause</li>



<li>smallest YAML-only fix, if any</li>



<li>exact YAML lines to change</li>



<li>whether I should validate locally or push directly</li>
</ol>



<p class="wp-block-paragraph">Do not edit files.<br />Stop after the analysis.</p>
</blockquote>



<p class="wp-block-paragraph">This worked.</p>



<p class="wp-block-paragraph">Copilot CLI used the GitHub CLI log and found the real failure:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">System.IO.IOException:</div><div class="cm-line">The process cannot access the file</div><div class="cm-line">C:\Users\runneradmin\AppData\Local\ElBruno.NetAgent\config.json</div><div class="cm-line">because it is being used by another process.</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The failing test was:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-variableName">SafetyGuardrailTests</span>.<span class="tok-variableName">SafetyChain_DefaultConfig_AllowsNoLiveExecution</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The important part: Copilot correctly concluded that this was&nbsp;<strong>not</strong>&nbsp;a YAML problem.</p>



<p class="wp-block-paragraph">It was a test isolation issue.</p>



<p class="wp-block-paragraph">The test was using the real user config path under&nbsp;<code>%LOCALAPPDATA%</code>, and in CI that file could be locked by another test/process.</p>



<p class="wp-block-paragraph">The fix was to use a unique temporary config directory for that test.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The CI fix</h2>



<p class="wp-block-paragraph">The production behavior needed to stay unchanged.</p>



<p class="wp-block-paragraph">So the fix was small:</p>



<ol class="wp-block-list">
<li>Add an optional config directory override to&nbsp;<code>ConfigurationService</code>.</li>



<li>Keep the default path as&nbsp;<code>%LOCALAPPDATA%\ElBruno.NetAgent</code>.</li>



<li>In the failing test, pass a unique temp directory.</li>



<li>Clean it up after the test.</li>
</ol>



<p class="wp-block-paragraph">Conceptually:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-variableName">ConfigurationService</span>(</div><div class="cm-line">    <span class="tok-variableName">ILogger</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ConfigurationService</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">logger</span>,</div><div class="cm-line">    <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-variableName">configDirectoryOverride</span> <span class="tok-operator">=</span> <span class="tok-atom">null</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">_logger</span> <span class="tok-operator">=</span> <span class="tok-variableName">logger</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">baseDirectory</span> <span class="tok-operator">=</span> <span class="tok-variableName">configDirectoryOverride</span></div><div class="cm-line">        <span class="tok-operator">??</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(</div><div class="cm-line">            <span class="tok-variableName">Environment</span>.<span class="tok-variableName">GetFolderPath</span>(<span class="tok-variableName">Environment</span>.<span class="tok-variableName">SpecialFolder</span>.<span class="tok-variableName">LocalApplicationData</span>),</div><div class="cm-line">            <span class="tok-string">&quot;ElBruno.NetAgent&quot;</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-variableName">_configurationDirectory</span> <span class="tok-operator">=</span> <span class="tok-variableName">baseDirectory</span>;</div><div class="cm-line">    <span class="tok-variableName">_configurationPath</span> <span class="tok-operator">=</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(<span class="tok-variableName">_configurationDirectory</span>, <span class="tok-string">&quot;config.json&quot;</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And in the test:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">tempDirectory</span> <span class="tok-operator">=</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(</div><div class="cm-line">    <span class="tok-variableName">Path</span>.<span class="tok-variableName">GetTempPath</span>(),</div><div class="cm-line">    <span class="tok-string">&quot;ElBruno.NetAgent.Test-&quot;</span> <span class="tok-operator">+</span> <span class="tok-typeName">Guid</span>.<span class="tok-variableName">NewGuid</span>().<span class="tok-variableName">ToString</span>(<span class="tok-string">&quot;N&quot;</span>));</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">Directory</span>.<span class="tok-variableName">CreateDirectory</span>(<span class="tok-variableName">tempDirectory</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">try</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">configService</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ConfigurationService</span>(<span class="tok-variableName">_configLogger</span>, <span class="tok-variableName">tempDirectory</span>);</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">options</span> <span class="tok-operator">=</span> <span class="tok-variableName">configService</span>.<span class="tok-variableName">LoadConfiguration</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-comment">// assertions...</span></div><div class="cm-line">}</div><div class="cm-line"><span class="tok-keyword">finally</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-variableName">Directory</span>.<span class="tok-variableName">Exists</span>(<span class="tok-variableName">tempDirectory</span>))</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">Directory</span>.<span class="tok-variableName">Delete</span>(<span class="tok-variableName">tempDirectory</span>, <span class="tok-variableName">recursive</span>: <span class="tok-atom">true</span>);</div><div class="cm-line">    }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">After this fix:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Local test passed<br />GitHub Actions passed<br />CI packaging validation succeeded</p>
</blockquote>



<p class="wp-block-paragraph">That was a very nice moment in the experiment.</p>



<p class="wp-block-paragraph">The local model diagnosed a real CI failure using real logs, then helped apply a minimal fix.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Permissions mattered more than expected</h2>



<p class="wp-block-paragraph">Another lesson: tool permissions are part of the system.</p>



<p class="wp-block-paragraph">At one point, Copilot CLI could read files but could not write them.</p>



<p class="wp-block-paragraph">The logs showed repeated failures like:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Permission denied and could not request permission from user</p>
</blockquote>



<p class="wp-block-paragraph">It tried:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">edit tool<br />PowerShell write<br />Python write<br />Copy-Item<br />regex replace<br />backup file</p>
</blockquote>



<p class="wp-block-paragraph">That was a useful failure.</p>



<p class="wp-block-paragraph">It showed that the model understood the fix, but the Copilot CLI tool environment could not write files.</p>



<p class="wp-block-paragraph">After enabling:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">/allow-all on</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Copilot CLI could edit files successfully.</p>



<p class="wp-block-paragraph">But there was a tradeoff.</p>



<p class="wp-block-paragraph">With full permissions enabled, I had to watch the run more carefully.</p>



<p class="wp-block-paragraph">The practical rule became:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">If you see &#8220;Continuing autonomously&#8221; after the summary, stop it.<br />If you see read_agent, stop it.<br />If you see SQL/todo tracking, stop it.<br />If it touches files outside the allowed list, stop it.<br />If it tries to publish, stop it.</p>
</blockquote>



<p class="wp-block-paragraph">This is not “fire and forget.”</p>



<p class="wp-block-paragraph">This is human-guided automation.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Packaging, icon generation, and t2i</h2>



<p class="wp-block-paragraph">The experiment also included preparing the NuGet package.</p>



<p class="wp-block-paragraph">No publishing yet. Only validation.</p>



<p class="wp-block-paragraph">The package included:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Package metadata<br />README.md<br />NuGet icon<br />local .nupkg generation<br />static package smoke test<br />CI packaging validation</p>
</blockquote>



<p class="wp-block-paragraph">For the package icon, I used the local&nbsp;<code>t2i</code>&nbsp;skill and the&nbsp;<code>ElBruno.Text2Image.Cli</code>&nbsp;package:</p>



<p class="wp-block-paragraph"><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli">https://www.nuget.org/packages/ElBruno.Text2Image.Cli</a></p>



<p class="wp-block-paragraph">The image generation prompt was something like:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Modern square app icon for &#8220;ElBruno.NetAgent&#8221;, a Windows-first network tray utility. Minimal semi-flat vector style. Central symbol combining Wi-Fi arcs, a small network switch/router, and a subtle shield/checkmark representing safe dry-run mode. Clean dark blue and cyan palette, high contrast, rounded square background, no text, no letters, no tiny details, readable at small NuGet package icon size.</p>
</blockquote>



<p class="wp-block-paragraph">The generated asset was wired into the project file as the NuGet package icon.</p>



<p class="wp-block-paragraph">The local package smoke test checked:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">package exists<br />README.md exists in package<br />icon exists in package<br />.nuspec contains icon metadata<br />packaged DLL exists<br />dry-run references exist</p>
</blockquote>



<p class="wp-block-paragraph">The smoke test was intentionally static.</p>



<p class="wp-block-paragraph">It did not launch the app. It did not mutate network state. It did not require admin permissions.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">CI packaging validation</h2>



<p class="wp-block-paragraph">The GitHub Actions workflow was created to validate packaging without publishing.</p>



<p class="wp-block-paragraph">It validates:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">restore<br />build<br />test<br />pack<br />local package smoke test<br />artifact upload</p>
</blockquote>



<p class="wp-block-paragraph">No secrets. No NuGet publishing. No app execution that mutates network state.</p>



<p class="wp-block-paragraph">A simplified version:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-yaml"><div class="cm-line"><span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> CI - Packaging Validation (No Publish)</div><div class="cm-line"></div><div class="cm-line"><span class="tok-propertyName tok-definition">on</span><span class="tok-punctuation">:</span></div><div class="cm-line">  <span class="tok-propertyName tok-definition">push</span><span class="tok-punctuation">:</span></div><div class="cm-line">    <span class="tok-propertyName tok-definition">branches</span><span class="tok-punctuation">:</span></div><div class="cm-line">      <span class="tok-punctuation">-</span> main</div><div class="cm-line">  <span class="tok-propertyName tok-definition">pull_request</span><span class="tok-punctuation">:</span></div><div class="cm-line">  <span class="tok-propertyName tok-definition">workflow_dispatch</span><span class="tok-punctuation">:</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-propertyName tok-definition">jobs</span><span class="tok-punctuation">:</span></div><div class="cm-line">  <span class="tok-propertyName tok-definition">packaging</span><span class="tok-punctuation">:</span></div><div class="cm-line">    <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> CI Packaging Validation</div><div class="cm-line">    <span class="tok-propertyName tok-definition">runs-on</span><span class="tok-punctuation">:</span> windows-latest</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-propertyName tok-definition">steps</span><span class="tok-punctuation">:</span></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Checkout</div><div class="cm-line">        <span class="tok-propertyName tok-definition">uses</span><span class="tok-punctuation">:</span> actions/checkout@v4</div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Setup .NET 10</div><div class="cm-line">        <span class="tok-propertyName tok-definition">uses</span><span class="tok-punctuation">:</span> actions/setup-dotnet@v4</div><div class="cm-line">        <span class="tok-propertyName tok-definition">with</span><span class="tok-punctuation">:</span></div><div class="cm-line">          <span class="tok-propertyName tok-definition">dotnet-version</span><span class="tok-punctuation">:</span> <span class="tok-string">&apos;10.0.x&apos;</span></div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Restore</div><div class="cm-line">        <span class="tok-propertyName tok-definition">run</span><span class="tok-punctuation">:</span> dotnet restore .\ElBruno.NetAgent.sln</div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Build</div><div class="cm-line">        <span class="tok-propertyName tok-definition">run</span><span class="tok-punctuation">:</span> dotnet build .\ElBruno.NetAgent.sln --configuration Release --no-restore</div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Test</div><div class="cm-line">        <span class="tok-propertyName tok-definition">run</span><span class="tok-punctuation">:</span> dotnet test .\ElBruno.NetAgent.sln --configuration Release --no-build --verbosity minimal</div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Pack</div><div class="cm-line">        <span class="tok-propertyName tok-definition">run</span><span class="tok-punctuation">:</span> dotnet pack .\src\ElBruno.NetAgent\ElBruno.NetAgent.csproj --configuration Release --no-build --output .\artifacts</div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Smoke test local package</div><div class="cm-line">        <span class="tok-propertyName tok-definition">shell</span><span class="tok-punctuation">:</span> pwsh</div><div class="cm-line">        <span class="tok-propertyName tok-definition">run</span><span class="tok-punctuation">:</span> .\scripts\smoke-test-local-package.ps1 -PackagePath .\artifacts\ElBruno.NetAgent.0.1.0.nupkg</div><div class="cm-line"></div><div class="cm-line">      <span class="tok-punctuation">-</span> <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> Upload package artifact</div><div class="cm-line">        <span class="tok-propertyName tok-definition">uses</span><span class="tok-punctuation">:</span> actions/upload-artifact@v4</div><div class="cm-line">        <span class="tok-propertyName tok-definition">with</span><span class="tok-punctuation">:</span></div><div class="cm-line">          <span class="tok-propertyName tok-definition">name</span><span class="tok-punctuation">:</span> ElBruno.NetAgent-nupkg</div><div class="cm-line">          <span class="tok-propertyName tok-definition">path</span><span class="tok-punctuation">:</span> artifacts/*.nupkg</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This gave the repo a safe release-readiness gate without publishing anything.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Safety guardrails were non-negotiable</h2>



<p class="wp-block-paragraph">Because this project is about network behavior, safety had to be part of the experiment from the beginning.</p>



<p class="wp-block-paragraph">The model was repeatedly instructed:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Do not execute real network changes.<br />Keep controller behavior dry-run only.<br />Do not publish to NuGet.<br />Do not add live network mutation.<br />Do not change safety behavior.</p>
</blockquote>



<p class="wp-block-paragraph">The app added guardrails such as:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">DryRunMode = true by default<br />LiveModeAllowed = false by default<br />unsafe config rejected<br />audit logs mark dry-run actions<br />live mode blocked<br />network mutation not implemented</p>
</blockquote>



<p class="wp-block-paragraph">The tests also checked these behaviors.</p>



<p class="wp-block-paragraph">That was important because local agentic coding is powerful, but the app domain matters.</p>



<p class="wp-block-paragraph">If the app can affect a machine’s network behavior, dry-run safety is not optional.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Prompt samples that worked well</h2>



<h3 class="wp-block-heading">1. Diagnostic prompt for GitHub Actions</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">We are continuing the ElBruno.NetAgent project.</p>



<p class="wp-block-paragraph">Goal:<br />Diagnose a failing GitHub Actions workflow using minimal tool-assisted analysis.</p>



<p class="wp-block-paragraph">Repo:<br />C:\src\ElBruno.NetAgent</p>



<p class="wp-block-paragraph">Failed GitHub Actions job:<br /><a href="https://github.com/brunoghcpft-oss/ElBruno.NetAgent/actions/runs//job/" rel="nofollow">https://github.com/brunoghcpft-oss/ElBruno.NetAgent/actions/runs//job/</a></p>



<p class="wp-block-paragraph">Important:</p>



<ul class="wp-block-list">
<li>gh auth is configured.</li>



<li>Do not edit files.</li>



<li>Do not run build/test/pack.</li>



<li>Do not use SQUAD.</li>



<li>Do not use task agents.</li>



<li>Do not use read_agent.</li>



<li>Do not use SQL or todo tracking.</li>



<li>Do not continue autonomously after the analysis.</li>



<li>Do not propose fixes without citing exact evidence from the CI log.</li>



<li>Use at most 4 shell commands.</li>



<li>Stop after the analysis.</li>
</ul>



<p class="wp-block-paragraph">Run only these commands:</p>



<ol class="wp-block-list">
<li>Get-Content ..github\workflows\ci.yml</li>



<li>Get-Content .\tests\ElBruno.NetAgent.Tests\ElBruno.NetAgent.Tests.csproj</li>



<li>Get-Content .\src\ElBruno.NetAgent\ElBruno.NetAgent.csproj</li>



<li>gh run view &#8211;job &#8211;log-failed</li>
</ol>



<p class="wp-block-paragraph">Return only:</p>



<ol class="wp-block-list">
<li>exact failing evidence from the CI log</li>



<li>likely root cause</li>



<li>smallest YAML-only fix, if any</li>



<li>exact YAML lines to change</li>



<li>whether I should validate locally or push directly</li>
</ol>



<p class="wp-block-paragraph">Then stop. Do not edit anything.</p>
</blockquote>



<h3 class="wp-block-heading">2. Minimal implementation prompt</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Apply this exact minimal fix.</p>



<p class="wp-block-paragraph">Do not diagnose.<br />Do not use agents.<br />Do not use read_agent.<br />Do not use SQL.<br />Do not use todo tracking.<br />Do not edit CI YAML.<br />Do not modify any files except:</p>



<ul class="wp-block-list">
<li>src\ElBruno.NetAgent\Services\Configuration\ConfigurationService.cs</li>



<li>tests\ElBruno.NetAgent.Tests\SafetyGuardrailTests.cs</li>
</ul>



<p class="wp-block-paragraph">If any write/edit tool fails with &#8220;Permission denied and could not request permission from user&#8221;, stop immediately. Do not try alternate write methods.</p>



<p class="wp-block-paragraph">Required change:<br />Add a test-only configuration directory override while preserving production default behavior.</p>



<p class="wp-block-paragraph">Validation:<br />Run only:<br />dotnet test .\tests\ElBruno.NetAgent.Tests\ElBruno.NetAgent.Tests.csproj &#8211;configuration Release &#8211;verbosity minimal</p>



<p class="wp-block-paragraph">After validation, summarize:</p>



<ul class="wp-block-list">
<li>exact files changed</li>



<li>test result</li>



<li>whether I should commit and push</li>
</ul>



<p class="wp-block-paragraph">Stop after summary.</p>
</blockquote>



<h3 class="wp-block-heading">3. Release readiness prompt</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">We are continuing the ElBruno.NetAgent project.</p>



<p class="wp-block-paragraph">Current repo:<br />C:\src\ElBruno.NetAgent</p>



<p class="wp-block-paragraph">Current status:</p>



<ul class="wp-block-list">
<li>Build passes.</li>



<li>Tests pass.</li>



<li>Pack passes.</li>



<li>Local package smoke test passes.</li>



<li>CI packaging validation passes.</li>



<li>No NuGet publishing has been performed.</li>



<li>The app remains dry-run only.</li>



<li>Live mode is blocked by default.</li>



<li>No real network mutation must be executed.</li>
</ul>



<p class="wp-block-paragraph">Important constraints:</p>



<ul class="wp-block-list">
<li>Do not use SQUAD.</li>



<li>Do not use task agents.</li>



<li>Do not use read_agent.</li>



<li>Do not use SQL or todo tracking.</li>



<li>Do not publish to NuGet.</li>



<li>Do not execute the app in a way that mutates network state.</li>



<li>Do not change dry-run safety behavior.</li>



<li>Do not add live network mutation.</li>



<li>Keep the phase bounded.</li>
</ul>



<p class="wp-block-paragraph">Implement Release Candidate Readiness, No Publish.</p>



<p class="wp-block-paragraph">Requirements:</p>



<ol class="wp-block-list">
<li>Review release readiness docs.</li>



<li>Add or update docs\RELEASE_CHECKLIST.md.</li>



<li>Confirm package metadata.</li>



<li>Confirm safety warnings are documented.</li>



<li>Confirm publishing remains manual.</li>



<li>Do not add NuGet publishing workflow.</li>
</ol>



<p class="wp-block-paragraph">Validation:<br />Run only:<br />dotnet build .\ElBruno.NetAgent.sln &#8211;configuration Release &#8211;no-restore<br />dotnet test .\ElBruno.NetAgent.sln &#8211;configuration Release &#8211;no-build &#8211;verbosity minimal<br />dotnet pack .\src\ElBruno.NetAgent\ElBruno.NetAgent.csproj &#8211;configuration Release &#8211;no-build &#8211;output .\artifacts<br />powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\smoke-test-local-package.ps1 -PackagePath .\artifacts\ElBruno.NetAgent.0.1.0.nupkg</p>



<p class="wp-block-paragraph">After validation, summarize:</p>



<ul class="wp-block-list">
<li>files changed</li>



<li>build result</li>



<li>test result</li>



<li>pack result</li>



<li>smoke test result</li>



<li>whether this is ready for manual release review</li>
</ul>



<p class="wp-block-paragraph">Stop after summary.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What did not work perfectly</h2>



<p class="wp-block-paragraph">This is the part that makes the experiment honest.</p>



<p class="wp-block-paragraph">Several things did not work smoothly.</p>



<h3 class="wp-block-heading">Runaway generation</h3>



<p class="wp-block-paragraph">Sometimes LM Studio kept generating tokens without returning a useful answer.</p>



<p class="wp-block-paragraph">The fix was usually:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">lower output token limit<br />reduce context<br />restart Copilot CLI<br />restart LM Studio server if needed<br />make the prompt smaller<br />add a clear stop condition</p>
</blockquote>



<h3 class="wp-block-heading">read_agent despite instructions</h3>



<p class="wp-block-paragraph">Even when the prompt said:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Do not use read_agent.</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Copilot CLI sometimes attempted it anyway.</p>



<p class="wp-block-paragraph">When that happened, I stopped the run.</p>



<h3 class="wp-block-heading">SQL/todo tracking attempts</h3>



<p class="wp-block-paragraph">Copilot CLI sometimes tried to use SQL/todo tracking even when I explicitly said not to.</p>



<p class="wp-block-paragraph">This often failed because the tool call was malformed, and it burned time and tokens.</p>



<h3 class="wp-block-heading">Tool permission issues</h3>



<p class="wp-block-paragraph">Before&nbsp;<code>/allow-all on</code>, write tools were blocked.</p>



<p class="wp-block-paragraph">After&nbsp;<code>/allow-all on</code>, writing worked, but I had to watch the run closely.</p>



<h3 class="wp-block-heading">Long validation loops</h3>



<p class="wp-block-paragraph">When I asked Copilot to validate everything, it sometimes got stuck in long shell/output loops.</p>



<p class="wp-block-paragraph">A better pattern was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Copilot edits.<br />Copilot runs one targeted validation.<br />Human runs full validation manually.</p>
</blockquote>



<h3 class="wp-block-heading">Bigger context sometimes made things worse</h3>



<p class="wp-block-paragraph">Large context helped for docs and synthesis.</p>



<p class="wp-block-paragraph">But for CI diagnosis, smaller context was better.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Final result</h2>



<p class="wp-block-paragraph">By the end of this experiment, the project reached a solid validation state:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Build: passed<br />Tests: 125/125 passed<br />Pack: passed<br />Smoke test: passed<br />CI: passed<br />NuGet publish: not performed<br />Network mutation: not performed<br />Dry-run: preserved</p>
</blockquote>



<p class="wp-block-paragraph">The GitHub Actions workflow validates:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">restore<br />build<br />test<br />pack<br />local package smoke test<br />artifact upload</p>
</blockquote>



<p class="wp-block-paragraph">The app remains safe:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">dry-run by default<br />live mode blocked<br />no real network mutation</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What I learned</h2>



<p class="wp-block-paragraph">The GPU did not make the local model magical.</p>



<p class="wp-block-paragraph">But it did change the workflow from:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">This is technically possible.</p>
</blockquote>



<p class="wp-block-paragraph">to:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">This can be useful if you control the scope.</p>
</blockquote>



<p class="wp-block-paragraph">The winning formula was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">GitHub Copilot CLI</p>



<ul class="wp-block-list">
<li>LM Studio</li>



<li>Qwen local</li>



<li>GPU</li>



<li>small phases</li>



<li>strict prompts</li>



<li>build/test validation</li>



<li>human checkpoints</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">The dangerous formula was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">huge prompt</p>



<ul class="wp-block-list">
<li>huge context</li>



<li>full autonomy</li>



<li>unclear stop condition</li>



<li>long validation loops</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">The best use case was not full autonomous coding.</p>



<p class="wp-block-paragraph">The best use case was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">tool-assisted development<br />bounded implementation<br />targeted diagnosis<br />human-controlled validation</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">My final take</h2>



<p class="wp-block-paragraph">If I needed to ship this as fast as possible, I would use a powerful cloud model.</p>



<p class="wp-block-paragraph">No debate.</p>



<p class="wp-block-paragraph">A cloud model would likely be faster, more stable, and easier to orchestrate.</p>



<p class="wp-block-paragraph">But that was not the point of this experiment.</p>



<p class="wp-block-paragraph">The point was to understand whether a local model could participate in a real developer workflow through GitHub Copilot CLI.</p>



<p class="wp-block-paragraph">And the answer is:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Yes, but with constraints.</p>
</blockquote>



<p class="wp-block-paragraph">Local models are not yet the easiest path.</p>



<p class="wp-block-paragraph">They are not always the fastest path.</p>



<p class="wp-block-paragraph">But they are becoming good enough for serious experimentation, especially when you care about control, offline capability, privacy, and understanding how agentic coding behaves under real constraints.</p>



<p class="wp-block-paragraph">The human remains the control plane.</p>



<p class="wp-block-paragraph">And honestly, that is probably a good thing.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Cloud models are still faster and more convenient.<br />Local models are becoming good enough to be dangerous — in the best possible way.</p>
</blockquote>



<h2 class="wp-block-heading">Resources</h2>



<p class="wp-block-paragraph">Here are the main resources, tools, and references mentioned in this experiment.</p>



<ul class="wp-block-list">
<li><strong>ElBruno.NetAgent repository</strong><br /><a href="https://github.com/brunoghcpft-oss/ElBruno.NetAgent">https://github.com/brunoghcpft-oss/ElBruno.NetAgent</a></li>



<li><strong>Previous CPU-only experiment</strong><br /><a href="https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/">https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/</a></li>



<li><strong>GitHub Copilot CLI documentation</strong><br /><a href="https://docs.github.com/en/copilot/how-tos/copilot-cli">https://docs.github.com/en/copilot/how-tos/copilot-cli</a></li>



<li><strong>Using GitHub Copilot CLI</strong><br /><a href="https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli">https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli</a></li>



<li><strong>GitHub Copilot CLI product page</strong><br /><a href="https://github.com/features/copilot/cli">https://github.com/features/copilot/cli</a></li>



<li><strong>LM Studio</strong><br /><a href="https://lmstudio.ai/">https://lmstudio.ai/</a></li>



<li><strong>LM Studio GitHub organization</strong><br /><a href="https://github.com/lmstudio-ai">https://github.com/lmstudio-ai</a></li>



<li><strong>Qwen3.6-35B-A3B model on Hugging Face</strong><br /><a href="https://huggingface.co/Qwen/Qwen3.6-35B-A3B">https://huggingface.co/Qwen/Qwen3.6-35B-A3B</a></li>



<li><strong>LM Studio community GGUF for Qwen3.6-35B-A3B</strong><br /><a href="https://huggingface.co/lmstudio-community/Qwen3.6-35B-A3B-GGUF">https://huggingface.co/lmstudio-community/Qwen3.6-35B-A3B-GGUF</a></li>



<li><strong>Qwen3.6-35B-A3B announcement</strong><br /><a href="https://qwen.ai/blog?id=qwen3.6-35b-a3b">https://qwen.ai/blog?id=qwen3.6-35b-a3b</a></li>



<li><strong>Qwen3.6 GitHub repository</strong><br /><a href="https://github.com/QwenLM/Qwen3.6">https://github.com/QwenLM/Qwen3.6</a></li>



<li><strong>.NET downloads</strong><br /><a href="https://dotnet.microsoft.com/en-us/download">https://dotnet.microsoft.com/en-us/download</a></li>



<li><strong>GitHub CLI</strong><br /><a href="https://cli.github.com/">https://cli.github.com/</a></li>



<li><strong>GitHub Actions documentation</strong><br /><a href="https://docs.github.com/en/actions">https://docs.github.com/en/actions</a></li>



<li><strong>NuGet package: ElBruno.Text2Image.Cli</strong><br /><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli">https://www.nuget.org/packages/ElBruno.Text2Image.Cli</a></li>



<li><strong>NuGet documentation</strong><br /><a href="https://learn.microsoft.com/en-us/nuget/">https://learn.microsoft.com/en-us/nuget/</a></li>



<li><strong>xUnit.net</strong><br /><a href="https://xunit.net/">https://xunit.net/</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/06/running-github-copilot-cli-offline-with-local-models-gpu-edition/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40289</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-5-2026-04_09_28-pm-small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/chatgpt-image-may-5-2026-04_09_28-pm-small.png">
			<media:title type="html">ChatGPT Image May 5, 2026, 04_09_28 PM-small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/image.png?w=1024"/>
	</item>
		<item>
		<title>Running GitHub Copilot CLI Offline with Local Models: A CPU-Only Reality Check</title>
		<link>https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/</link>
					<comments>https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Sun, 03 May 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub Copilot CLI]]></category>
		<category><![CDATA[VM]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40267</guid>

					<description><![CDATA[TL;DR I tested GitHub Copilot CLI running offline with local models on a CPU-only Microsoft Dev Box. The short version: Or, in one sentence: CPU-only can be your copilot. Just do not promote it to engineering manager yet 😅 The experiment goal This experiment started with a simple question: Can I use GitHub Copilot CLI [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading"></h2>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="571" src="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png?w=1024" alt="" class="wp-image-40283" srcset="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<h2 class="wp-block-heading">TL;DR</h2>



<p class="wp-block-paragraph">I tested <strong>GitHub Copilot CLI running offline with local models</strong> on a <strong>CPU-only Microsoft Dev Box</strong>.</p>



<p class="wp-block-paragraph">The short version:</p>



<ul class="wp-block-list">
<li><strong>Ollama worked</strong> for basic offline prompts, but struggled with long-context agentic workflows and tool execution on CPU-only hardware.</li>



<li><strong>LM Studio worked better</strong> as the local OpenAI-compatible provider.</li>



<li><code>google/gemma-4-e2b</code> successfully executed Copilot CLI PowerShell tools.</li>



<li><strong>32K and 64K context</strong> were the practical sweet spot for guided development.</li>



<li><strong>131K context worked</strong> as a stress test, but it was not practical as a daily CPU-only workflow.</li>



<li><strong>Copilot CLI interactive mode worked well</strong> with small, explicit, validated tasks.</li>



<li><strong>SQUAD/autopilot long workflows were not reliable enough</strong> on CPU-only hardware.</li>



<li>Final takeaway: local Copilot CLI works on CPU-only, but as a guided pair-programming workflow, not as a fully autonomous engineering team.</li>
</ul>



<p class="wp-block-paragraph">Or, in one sentence:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">CPU-only can be your copilot. Just do not promote it to engineering manager yet <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The experiment goal</h2>



<p class="wp-block-paragraph">This experiment started with a simple question:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Can I use GitHub Copilot CLI with a local model, offline, to work on a real .NET project?</p>
</blockquote>



<p class="wp-block-paragraph">The project was:</p>



<p class="wp-block-paragraph"><a href="https://github.com/brunoghcpft-oss/ElBruno.NetAgent">https://github.com/brunoghcpft-oss/ElBruno.NetAgent</a></p>



<p class="wp-block-paragraph">The bigger idea was also very attractive:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">GitHub Copilot CLI<br />+ SQUAD<br />+ local models<br />+ GitHub Copilot Free Tier<br />+ .NET<br />+ offline/local execution</p>
</blockquote>



<p class="wp-block-paragraph">That combination matters because it hints at a future where developers can experiment with local agentic workflows without always depending on cloud-hosted inference.</p>



<p class="wp-block-paragraph">But I did not want to only ask the model questions.</p>



<p class="wp-block-paragraph">I wanted to know if it could perform real developer tasks:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">dotnet new sln<br />dotnet new wpf<br />dotnet new xunit<br />dotnet build<br />dotnet test<br />git status<br />create files<br />update docs<br />work inside Copilot CLI interactively<br />use tools</p>
</blockquote>



<p class="wp-block-paragraph">That was the real test.</p>



<p class="wp-block-paragraph">Not “Can the model answer?”</p>



<p class="wp-block-paragraph">But:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Can the model act through Copilot CLI tools and help build a real .NET project?</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">First attempt: Ollama</h2>



<p class="wp-block-paragraph">The first local provider I tested was <strong>Ollama</strong>.</p>



<p class="wp-block-paragraph">Ollama is a great tool for running local models. It is simple, developer-friendly, and works very well for many local AI experiments.</p>



<p class="wp-block-paragraph">The initial Copilot CLI setup looked like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_BASE_URL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;http://localhost:11434/v1&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_TYPE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;openai&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_MODEL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;qwen3.5&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_OFFLINE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;true&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">I tried several models, including:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">qwen3.5<br />qwen3.5:4b<br />llama3.2<br />devstral-small-2<br />gemma4:e2b<br />qwen2.5-coder</p>
</blockquote>



<p class="wp-block-paragraph">The first big surprise was how large a “simple” Copilot CLI prompt actually becomes.</p>



<p class="wp-block-paragraph">Even something like:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Reply exactly: OK</p>
</blockquote>



<p class="wp-block-paragraph">is not just that text. Copilot CLI also includes system instructions, tool definitions, environment data, agent instructions, and other context.</p>



<p class="wp-block-paragraph">So a tiny user prompt can quickly become a large request.</p>



<p class="wp-block-paragraph">With Ollama on this CPU-only machine, I saw a consistent pattern:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Small context → truncation or failure<br />Larger context → very slow prompt processing<br />Tool execution → unreliable<br />CPU-only → retries, transient errors, long waits</p>
</blockquote>



<p class="wp-block-paragraph">Ollama did work in some scenarios. It responded to simple prompts and could be useful for ask-style interactions.</p>



<p class="wp-block-paragraph">But for Copilot CLI tool execution and agentic workflows, it was not reliable enough on this hardware.</p>



<p class="wp-block-paragraph">My conclusion for Ollama:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Ollama worked, but mostly for ask mode. In this CPU-only setup, it was not a good fit for full Copilot CLI agentic coding.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The context problem</h2>



<p class="wp-block-paragraph">One of the most important lessons was about <strong>context size</strong>.</p>



<p class="wp-block-paragraph">Copilot CLI does not only need room for your prompt. It also needs context for:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">system instructions<br />tool definitions<br />agent instructions<br />workspace information<br />command outputs<br />conversation history<br />MCP/tool metadata<br />custom agent context</p>
</blockquote>



<p class="wp-block-paragraph">The GitHub Copilot CLI best practices mention the value of models with large context windows for agentic work:</p>



<p class="wp-block-paragraph"><a href="https://docs.github.com/en/copilot/how-tos/copilot-cli/cli-best-practices">https://docs.github.com/en/copilot/how-tos/copilot-cli/cli-best-practices</a></p>



<p class="wp-block-paragraph">The command reference is also useful when exploring Copilot CLI behavior and options:</p>



<p class="wp-block-paragraph"><a href="https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference">https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-command-reference</a></p>



<p class="wp-block-paragraph">A task that looks small to us can become a much larger request for the CLI.</p>



<p class="wp-block-paragraph">For example:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Create a WPF project and run tests.</p>
</blockquote>



<p class="wp-block-paragraph">That sounds simple. But an agentic CLI workflow may need to include repo state, tools, instructions, command outputs, and validation steps.</p>



<p class="wp-block-paragraph">More context helps because the request fits better.</p>



<p class="wp-block-paragraph">But on CPU-only hardware, more context is not free:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">more context<br />= more prompt prefill<br />= more CPU work<br />= more waiting<br />= more chances for retries or timeouts</p>
</blockquote>



<p class="wp-block-paragraph">So context solved one problem and created another.</p>



<p class="wp-block-paragraph">The request could fit.</p>



<p class="wp-block-paragraph">But now the CPU had to process it.</p>



<p class="wp-block-paragraph">Slowly.</p>



<p class="wp-block-paragraph">Very slowly.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Moving from Ollama to LM Studio</h2>



<p class="wp-block-paragraph">The experiment changed when I moved from Ollama to <strong>LM Studio</strong>.</p>



<p class="wp-block-paragraph">LM Studio provides a local OpenAI-compatible server. In this setup, the endpoint was:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">http://localhost:1234/v1</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The Copilot CLI environment variables became:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_BASE_URL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;http://localhost:1234/v1&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_TYPE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;openai&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_MODEL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;google/gemma-4-e2b&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_OFFLINE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;true&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The model I used for the successful runs was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">google/gemma-4-e2b</p>
</blockquote>



<p class="wp-block-paragraph">One LM Studio setting was critical:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Max Concurrent Predictions = 1</p>
</blockquote>



<p class="wp-block-paragraph">This matters because if the context length is set high but the server splits capacity across multiple concurrent predictions, each request may effectively get less usable context.</p>



<p class="wp-block-paragraph">For this experiment, I wanted one Copilot CLI request to get the full available context.</p>



<p class="wp-block-paragraph">The key LM Studio settings became:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Context Length: 32768, 65536, and 131072 during stress testing<br />Max Concurrent Predictions: 1<br />Keep Model in Memory: ON</p>
</blockquote>



<p class="wp-block-paragraph">LM Studio also gave me much better observability.</p>



<p class="wp-block-paragraph">With Ollama, I was mostly reading logs and guessing.</p>



<p class="wp-block-paragraph">With LM Studio, I could see the loaded model, server status, effective configuration, and live prompt processing progress.</p>



<p class="wp-block-paragraph">That mattered a lot.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">With LM Studio, I stopped guessing. I could see the prompt processing percentage moving.</p>
</blockquote>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="537" src="https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=1024" alt="" class="wp-image-40286" srcset="https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png 1890w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The first real tool execution</h2>



<p class="wp-block-paragraph">The first major breakthrough was not the model answering a question.</p>



<p class="wp-block-paragraph">The breakthrough was tool execution.</p>



<p class="wp-block-paragraph">I asked Copilot CLI to use the PowerShell tool and create a file.</p>



<p class="wp-block-paragraph">The first attempt almost worked, but failed because the tool call was missing a required field:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">&#8220;description&#8221;: Required</p>
</blockquote>



<p class="wp-block-paragraph">That was an important lesson.</p>



<p class="wp-block-paragraph">The model was trying to use the tool, but the function call was incomplete.</p>



<p class="wp-block-paragraph">So I changed the prompt pattern.</p>



<p class="wp-block-paragraph">The working pattern became:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Use the powershell tool. The powershell tool call must include both required fields: command and description. Run this exact command: . Use this description: . Do not only describe the action.</p>
</blockquote>



<p class="wp-block-paragraph">Example:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Use the powershell tool. The powershell tool call must include both required fields: command and description. Run this exact command: echo LMSTUDIO_E2B_TOOL_TEST &gt; lmstudio-e2b-tool-test.txt. Use this description: Create LM Studio tool test file. Do not only describe the action.</p>
</blockquote>



<p class="wp-block-paragraph">This time, Copilot CLI executed the command.</p>



<p class="wp-block-paragraph">The file was created.</p>



<p class="wp-block-paragraph">That was the first real “OK”.</p>



<p class="wp-block-paragraph">Not just:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The model answered.</p>
</blockquote>



<p class="wp-block-paragraph">But:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The model used Copilot CLI tools to change the filesystem.</p>
</blockquote>



<p class="wp-block-paragraph">That changed the experiment.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">From 16K to 32K</h2>



<p class="wp-block-paragraph">At 16K context, basic tool execution worked.</p>



<p class="wp-block-paragraph">But when I tried to create a larger Markdown file using multiline content, the result became unreliable. The model claimed success, but the file was not created correctly.</p>



<p class="wp-block-paragraph">This taught me something important:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">More context is not the only problem. Command complexity matters too.</p>
</blockquote>



<p class="wp-block-paragraph">Long multiline here-strings, large embedded Markdown blocks, and complex PowerShell escaping were too much for this local setup.</p>



<p class="wp-block-paragraph">So I moved to a stricter execution rule:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">one prompt<br />one tool<br />one bounded command<br />one validation</p>
</blockquote>



<p class="wp-block-paragraph">Then I increased the context to 32K and started running real Phase 0-style .NET setup tasks.</p>



<p class="wp-block-paragraph">This worked.</p>



<p class="wp-block-paragraph">Copilot CLI successfully executed commands like:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">new</span> <span class="tok-variableName">sln</span> <span class="tok-operator">-</span><span class="tok-variableName">n</span> <span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">New-Item</span> <span class="tok-operator">-</span><span class="tok-variableName">ItemType</span> <span class="tok-variableName">Directory</span> <span class="tok-operator">-</span><span class="tok-variableName">Force</span> <span class="tok-operator">-</span><span class="tok-variableName">Path</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">src</span></div><div class="cm-line"><span class="tok-variableName">New-Item</span> <span class="tok-operator">-</span><span class="tok-variableName">ItemType</span> <span class="tok-variableName">Directory</span> <span class="tok-operator">-</span><span class="tok-variableName">Force</span> <span class="tok-operator">-</span><span class="tok-variableName">Path</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">tests</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">new</span> <span class="tok-variableName">wpf</span> <span class="tok-operator">-</span><span class="tok-variableName">n</span> <span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span> <span class="tok-operator">-</span><span class="tok-variableName">o</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">src</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">new</span> <span class="tok-variableName">xunit</span> <span class="tok-operator">-</span><span class="tok-variableName">n</span> <span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">Tests</span> <span class="tok-operator">-</span><span class="tok-variableName">o</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">tests</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">Tests</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">sln</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">sln</span> <span class="tok-variableName">add</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">src</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">csproj</span></div><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">sln</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">sln</span> <span class="tok-variableName">add</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">tests</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">Tests</span><span class="tok-punctuation">\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">Tests</span><span class="tok-punctuation">.</span><span class="tok-variableName">csproj</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And finally:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">build</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">sln</span></div><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">test</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">sln</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This was a big milestone.</p>



<p class="wp-block-paragraph">With LM Studio, Gemma 4 E2B, and explicit tool instructions, Copilot CLI created a real .NET solution and validated it.</p>



<p class="wp-block-paragraph">The runtime was not fast. Most commands took several minutes.</p>



<p class="wp-block-paragraph">But they worked.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The practical prompt pattern</h2>



<p class="wp-block-paragraph">The reliable prompt style was very explicit.</p>



<p class="wp-block-paragraph">For example:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Use the powershell tool. The powershell tool call must include both required fields: command and description.</p>



<p class="wp-block-paragraph">Run this exact command:<br />dotnet build .\ElBruno.NetAgent.sln</p>



<p class="wp-block-paragraph">Use this description:<br />Build ElBruno NetAgent solution.</p>



<p class="wp-block-paragraph">Do not only describe the action.</p>
</blockquote>



<p class="wp-block-paragraph">This is not elegant.</p>



<p class="wp-block-paragraph">But it was reliable.</p>



<p class="wp-block-paragraph">The important parts were:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">name the tool<br />state the required fields<br />give the exact command<br />give the description<br />tell it not to only describe the action<br />include validation when possible</p>
</blockquote>



<p class="wp-block-paragraph">A better workflow emerged:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">ask for one small task<br />execute one command<br />validate the result<br />continue</p>
</blockquote>



<p class="wp-block-paragraph">That was the first point where the experiment became useful.</p>



<p class="wp-block-paragraph">Not autonomous.</p>



<p class="wp-block-paragraph">But useful.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Moving to 64K context</h2>



<p class="wp-block-paragraph">After 32K worked, I tested 64K.</p>



<p class="wp-block-paragraph">The environment variables became:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_BASE_URL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;http://localhost:1234/v1&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_TYPE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;openai&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_MODEL</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;google/gemma-4-e2b&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;65536&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;512&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_OFFLINE</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;true&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">In LM Studio:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Context Length: 65536<br />Max Concurrent Predictions: 1</p>
</blockquote>



<p class="wp-block-paragraph">I tested a combined command:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">git</span> <span class="tok-variableName">status</span> <span class="tok-operator">--</span><span class="tok-variableName">short</span><span class="tok-punctuation">;</span> <span class="tok-variableName">dotnet</span> <span class="tok-variableName">test</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">sln</span><span class="tok-punctuation">;</span> <span class="tok-variableName">git</span> <span class="tok-variableName">status</span> <span class="tok-operator">--</span><span class="tok-variableName">short</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">It worked.</p>



<p class="wp-block-paragraph">The important observation was that 64K did not magically transform the model.</p>



<p class="wp-block-paragraph">The effective token usage was still around the same range as several 32K tests.</p>



<p class="wp-block-paragraph">So 64K gave the workflow more room, but it did not remove the need for disciplined prompts.</p>



<p class="wp-block-paragraph">My conclusion:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">64K worked, but it did not magically make the model smarter. It just gave the workflow more breathing room.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Testing Copilot CLI interactive mode</h2>



<p class="wp-block-paragraph">Up to this point, many tests used:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">copilot</span> <span class="tok-operator">-</span><span class="tok-variableName">p</span> <span class="tok-string">&quot;&lt;prompt&gt;&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That was useful for deterministic experiments, but it was not the workflow I really cared about.</p>



<p class="wp-block-paragraph">The real workflow should happen inside Copilot CLI.</p>



<p class="wp-block-paragraph">So I launched Copilot CLI interactively:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">copilot</span> <span class="tok-punctuation">`</span></div><div class="cm-line"><span class="tok-operator">--</span><span class="tok-variableName">disable-builtin-mcps</span> <span class="tok-punctuation">`</span></div><div class="cm-line"><span class="tok-operator">--</span><span class="tok-variableName">no-remote</span> <span class="tok-punctuation">`</span></div><div class="cm-line"><span class="tok-operator">--</span><span class="tok-variableName">yolo</span> <span class="tok-punctuation">`</span></div><div class="cm-line"><span class="tok-operator">--</span><span class="tok-variableName">stream</span> <span class="tok-variableName">on</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then I gave instructions inside the CLI.</p>



<p class="wp-block-paragraph">This also worked.</p>



<p class="wp-block-paragraph">I tested:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">git status &#8211;short<br />dotnet test .\ElBruno.NetAgent.sln<br />create folders<br />validate files</p>
</blockquote>



<p class="wp-block-paragraph">The interactive workflow was more natural.</p>



<p class="wp-block-paragraph">But the same rule still applied:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">small steps<br />explicit tool call<br />validation<br />no giant task requests</p>
</blockquote>



<p class="wp-block-paragraph">At this point, the conclusion was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Interactive Copilot CLI with a local model is usable on CPU-only hardware, as long as the work is guided and constrained.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The 131K context stress test</h2>



<p class="wp-block-paragraph">LM Studio showed that <code>google/gemma-4-e2b</code> could be configured with a context length of 131072 tokens.</p>



<p class="wp-block-paragraph">So I tried the big one.</p>



<p class="wp-block-paragraph">In LM Studio:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Context Length: 131072<br />Max Concurrent Predictions: 1</p>
</blockquote>



<p class="wp-block-paragraph">In PowerShell:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;131072&quot;</span></div><div class="cm-line"><span class="tok-variableName">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;1024&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then I ran Copilot CLI interactively and enabled autopilot.</p>



<p class="wp-block-paragraph">The stress test asked Copilot CLI to:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">inspect git status<br />inspect solution contents<br />run build<br />run tests<br />create a status file<br />validate the status file<br />show final git status</p>
</blockquote>



<p class="wp-block-paragraph">This worked well enough.</p>



<p class="wp-block-paragraph">The model executed build and tests, created the status file, and I manually validated the result.</p>



<p class="wp-block-paragraph">The generated status file contained:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">provider: LM Studio<br />model: google/gemma-4-e2b<br />context: 131072<br />execution mode: Copilot CLI interactive autopilot</p>
</blockquote>



<p class="wp-block-paragraph">Then I corrected it with a micro-prompt to add the missing result summary.</p>



<p class="wp-block-paragraph">Manual validation passed:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">Test-Path</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">docs</span><span class="tok-punctuation">\</span><span class="tok-variableName">PHASE0_STRESS_TEST_RESULT</span><span class="tok-punctuation">.</span><span class="tok-variableName">md</span></div><div class="cm-line"><span class="tok-variableName">Get-Content</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">docs</span><span class="tok-punctuation">\</span><span class="tok-variableName">PHASE0_STRESS_TEST_RESULT</span><span class="tok-punctuation">.</span><span class="tok-variableName">md</span></div><div class="cm-line"><span class="tok-variableName">git</span> <span class="tok-variableName">status</span> <span class="tok-operator">--</span><span class="tok-variableName">short</span></div><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">test</span> <span class="tok-punctuation">.\</span><span class="tok-variableName">ElBruno</span><span class="tok-punctuation">.</span><span class="tok-variableName">NetAgent</span><span class="tok-punctuation">.</span><span class="tok-variableName">sln</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">So, yes:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">131K context worked.</p>
</blockquote>



<p class="wp-block-paragraph">But there is an important nuance:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">131K worked technically. That does not mean 131K was practical on CPU-only hardware.</p>
</blockquote>



<p class="wp-block-paragraph">For the successful stress test, Copilot CLI did not actually need to consume the full 131K context. The larger context was available, but the workflow still depended on bounded tasks.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The SQUAD test</h2>



<p class="wp-block-paragraph">Finally, I tried the big question:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Can SQUAD run this as a more autonomous workflow?</p>
</blockquote>



<p class="wp-block-paragraph">This was the most interesting failure.</p>



<p class="wp-block-paragraph">I reactivated the SQUAD custom agent and asked it to:</p>



<ol class="wp-block-list">
<li>Inspect the repository.</li>



<li>Update the existing SQUAD execution rules with a local model execution rule.</li>



<li>Implement a simple WPF status display.</li>



<li>Build.</li>



<li>Test.</li>



<li>Show final git status.</li>



<li>Summarize the changes.</li>
</ol>



<p class="wp-block-paragraph">This is where CPU-only started to show the real limit.</p>



<p class="wp-block-paragraph">Copilot CLI selected the custom agent:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Selected custom agent: Squad</p>
</blockquote>



<p class="wp-block-paragraph">Then transient API errors started appearing.</p>



<p class="wp-block-paragraph">Then a tool call failed because it was missing the required <code>description</code> field:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">&#8220;description&#8221;: Required</p>
</blockquote>



<p class="wp-block-paragraph">After that, it executed <code>git status --short</code>, but then started claiming that the workflow had completed, including rule updates, build/test sequence, and feature integration.</p>



<p class="wp-block-paragraph">The problem: the log did not show reliable tool-call evidence for all those claimed actions.</p>



<p class="wp-block-paragraph">That is the danger zone.</p>



<p class="wp-block-paragraph">The model can say:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Done.</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">But as developers, we need:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Show me the tool output.<br />Show me the file diff.<br />Show me the build.<br />Show me the test result.</p>
</blockquote>



<p class="wp-block-paragraph">The LM Studio logs showed the other side of the problem: long SQUAD/autopilot workflows generate lots of reasoning and inflate the prompt. The model was processing a large conversation and spending a lot of time in reasoning rather than clean tool execution.</p>



<p class="wp-block-paragraph">This was the final conclusion for SQUAD on CPU-only:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">SQUAD/autopilot long workflows are not reliable enough on this hardware. The model can drift, retries can appear, malformed tool calls can happen, and it may claim completion without enough verified execution.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What worked well</h2>



<p class="wp-block-paragraph">This setup worked well for guided work:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">LM Studio</p>



<ul class="wp-block-list">
<li>google/gemma-4-e2b</li>



<li>Copilot CLI</li>



<li>offline mode</li>



<li>PowerShell tools</li>



<li>32K / 64K context</li>



<li>small tasks</li>



<li>manual validation</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">It successfully handled:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">dotnet new sln<br />dotnet new wpf<br />dotnet new xunit<br />dotnet sln add<br />dotnet build<br />dotnet test<br />git status<br />file creation<br />small docs updates<br />interactive Copilot CLI prompts<br />autopilot stress test with manual validation</p>
</blockquote>



<p class="wp-block-paragraph">That is a real result.</p>



<p class="wp-block-paragraph">It means local model workflows for .NET development are possible on CPU-only hardware.</p>



<p class="wp-block-paragraph">Slow, yes.</p>



<p class="wp-block-paragraph">But possible.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What did not work well</h2>



<p class="wp-block-paragraph">This setup did not work well for:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">long autonomous SQUAD execution<br />large “do the whole phase” prompts<br />complex multiline file generation<br />large here-strings<br />unattended autopilot loops<br />claims without validation<br />CPU-only 131K workflows as a daily mode</p>
</blockquote>



<p class="wp-block-paragraph">The main risks were:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">very slow prompt processing<br />transient API errors<br />malformed tool calls<br />missing required tool fields<br />reasoning drift<br />false completion claims<br />too much context for CPU-only hardware</p>
</blockquote>



<p class="wp-block-paragraph">The most important failure mode was not:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The model cannot answer.</p>
</blockquote>



<p class="wp-block-paragraph">The most important failure mode was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The model claims that work is complete, but the validated tool evidence is incomplete.</p>
</blockquote>



<p class="wp-block-paragraph">That is why manual validation is not optional.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The sweet spot</h2>



<p class="wp-block-paragraph">For this CPU-only VM, the sweet spot is:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Provider: LM Studio<br />Model: google/gemma-4-e2b<br />Context: 32K or 64K<br />Max Concurrent Predictions: 1<br />Copilot CLI: offline + no remote<br />Mode: interactive<br />Workflow: one bounded task at a time<br />Validation: always</p>
</blockquote>



<p class="wp-block-paragraph">The best working rule:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">One prompt.<br />One tool.<br />One bounded command.<br />One validation.</p>
</blockquote>



<p class="wp-block-paragraph">This is not as flashy as “fully autonomous AI developer.”</p>



<p class="wp-block-paragraph">But it is much more realistic.</p>



<p class="wp-block-paragraph">And it works.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Recommended Copilot CLI setup</h2>



<p class="wp-block-paragraph">This is the setup I would keep for guided local work:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">$env:COPILOT_PROVIDER_BASE_URL = &quot;http://localhost:1234/v1&quot;</div><div class="cm-line">$env:COPILOT_PROVIDER_TYPE = &quot;openai&quot;</div><div class="cm-line">$env:COPILOT_MODEL = &quot;google/gemma-4-e2b&quot;</div><div class="cm-line">$env:COPILOT_PROVIDER_MAX_PROMPT_TOKENS = &quot;65536&quot;</div><div class="cm-line">$env:COPILOT_PROVIDER_MAX_OUTPUT_TOKENS = &quot;512&quot;</div><div class="cm-line">$env:COPILOT_OFFLINE = &quot;true&quot;</div><div class="cm-line"></div><div class="cm-line">copilot `</div><div class="cm-line">  --disable-builtin-mcps `</div><div class="cm-line">  --no-remote `</div><div class="cm-line">  --yolo `</div><div class="cm-line">  --stream on</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then inside Copilot CLI, use prompts like this:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Use the powershell tool. The powershell tool call must include both required fields: command and description.</p>



<p class="wp-block-paragraph">Run this exact command:<br />dotnet test .\ElBruno.NetAgent.sln</p>



<p class="wp-block-paragraph">Use this description:<br />Run ElBruno NetAgent tests.</p>



<p class="wp-block-paragraph">Do not only describe the action.</p>
</blockquote>



<p class="wp-block-paragraph">Again, not elegant.</p>



<p class="wp-block-paragraph">But reliable.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Test environment</h2>



<p class="wp-block-paragraph">These results are based on one CPU-only virtual machine. They should not be treated as a universal benchmark for Copilot CLI, LM Studio, Ollama, or Gemma 4 E2B.</p>



<p class="wp-block-paragraph">Hardware and OS:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Environment: Microsoft Dev Box<br />Dev Box size: AMD 8 vCPU / 32 GB RAM / 1024 GB storage<br />Processor: AMD EPYC 7763 64-Core Processor @ 2.44 GHz<br />Installed RAM: 32.0 GB<br />Storage: 1.00 TB<br />GPU: None<br />Dedicated VRAM: None<br />System type: 64-bit operating system, x64-based processor<br />Operating system: Windows 11 Enterprise<br />Windows version: 25H2<br />OS build: 26200.8246</p>
</blockquote>



<p class="wp-block-paragraph">Experiment setup:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Shell: PowerShell<br />Main successful local model host: LM Studio<br />Main successful model: google/gemma-4-e2b<br />Other local model host tested: Ollama<br />Copilot account: GitHub Copilot Free Tier<br />Copilot CLI mode: offline / local provider<br />Project type: .NET WPF app + xUnit test project</p>
</blockquote>



<p class="wp-block-paragraph">The important detail is that this machine had <strong>no dedicated GPU</strong> and <strong>no dedicated VRAM</strong>.</p>



<p class="wp-block-paragraph">All local model inference happened on CPU.</p>



<p class="wp-block-paragraph">That is why long-context workflows, especially SQUAD/autopilot runs, became slow and fragile even when the model technically supported 131K context.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Final conclusion</h2>



<p class="wp-block-paragraph">This experiment changed my expectations.</p>



<p class="wp-block-paragraph">At the beginning, I thought the question was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Which local model works with Copilot CLI?</p>
</blockquote>



<p class="wp-block-paragraph">By the end, the real question was:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">What workflow makes local Copilot CLI reliable enough to be useful?</p>
</blockquote>



<p class="wp-block-paragraph">The answer was not just the model.</p>



<p class="wp-block-paragraph">The answer was the combination of:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">LM Studio</p>



<ul class="wp-block-list">
<li>Gemma 4 E2B</li>



<li>enough context</li>



<li>low concurrency</li>



<li>explicit tool instructions</li>



<li>small tasks</li>



<li>constant validation</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">On CPU-only hardware, the result is clear:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Good for guided pair programming.<br />Good for small .NET tasks.<br />Good for local/offline experiments.<br />Good for learning how agentic workflows behave.</p>



<p class="wp-block-paragraph">Not good for fully autonomous SQUAD execution.<br />Not good for long unattended implementation phases.<br />Not good for pretending CPU-only is a cloud GPU cluster.</p>
</blockquote>



<p class="wp-block-paragraph">Local AI is real.</p>



<p class="wp-block-paragraph">Offline developer workflows are real.</p>



<p class="wp-block-paragraph">Copilot CLI with local models is real.</p>



<p class="wp-block-paragraph">But autonomy still needs the right model, the right hardware, and a lot of discipline.</p>



<p class="wp-block-paragraph">CPU-only can be your copilot.</p>



<p class="wp-block-paragraph">Just do not promote it to engineering manager yet <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/05/03/running-github-copilot-cli-offline-with-local-models-a-cpu-only-reality-check/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40267</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png">
			<media:title type="html">Gemini_Generated_Image_wduyfswduyfswduy</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/gemini_generated_image_wduyfswduyfswduy.png?w=1024"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/05/ghcp-10-lmstudio-gemma4-128k-context-auto-pilot-run.png?w=1024"/>
	</item>
		<item>
		<title>OllamaMonitor — Monitor Your Local Ollama Runtime from the Windows Tray</title>
		<link>https://elbruno.com/2026/04/28/ollamamonitor-monitor-your-local-ollama-runtime-from-the-windows-tray/</link>
					<comments>https://elbruno.com/2026/04/28/ollamamonitor-monitor-your-local-ollama-runtime-from-the-windows-tray/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Tue, 28 Apr 2026 12:17:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[CLI Tools]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[NuGet Packages]]></category>
		<category><![CDATA[Ollama]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40260</guid>

					<description><![CDATA[Hi! If you&#8217;re running Ollama locally on Windows—whether you&#8217;re tinkering with LLMs, building local AI demos, or just curious about the overhead of large models—you&#8217;ve probably wondered: Is it still running? How much CPU is it chewing? Did that model load? Welcome to&#160;ElBruno.OllamaMonitor. It&#8217;s a no-frills system tray app that sits in your Windows notification area [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<figure class="wp-block-image size-large"><img loading="lazy" width="426" height="240" src="https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif?w=426" alt="" class="wp-image-40261" srcset="https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif 426w, https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif?w=300 300w" sizes="(max-width: 426px) 100vw, 426px" /></figure>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">If you&#8217;re running Ollama locally on Windows—whether you&#8217;re tinkering with LLMs, building local AI demos, or just curious about the overhead of large models—you&#8217;ve probably wondered: <em>Is it still running? How much CPU is it chewing? Did that model load?</em></p>



<p class="wp-block-paragraph">Welcome to&nbsp;<strong>ElBruno.OllamaMonitor</strong>.</p>



<p class="wp-block-paragraph">It&#8217;s a no-frills system tray app that sits in your Windows notification area and tells you, at a glance, exactly what your local Ollama instance is doing. No dashboards. No complexity. Just real-time status, resource metrics, and a floating details window when you need more info.</p>



<p class="wp-block-paragraph">ElBruno.OllamaMonitor sits in your Windows system tray and tells you:</p>



<ul class="wp-block-list">
<li><strong>Is Ollama running?</strong> A glance at the tray icon shows you the status.</li>



<li><strong>Is a model loaded?</strong> See what’s currently active.</li>



<li><strong>How much CPU, RAM, and GPU is it using?</strong> Real-time resource metrics from the Ollama process.</li>



<li><strong>Any errors?</strong> Get instant visual feedback if something’s wrong.</li>
</ul>



<p class="wp-block-paragraph">Perfect for:</p>



<ul class="wp-block-list">
<li>Local AI developers who need quick visibility into Ollama</li>



<li>Demo presenters who want to know resource impact in real-time</li>



<li>Anyone running large models locally who’s curious about the overhead</li>
</ul>



<h2 class="wp-block-heading">Installation<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#installation"></a></h2>



<h3 class="wp-block-heading">Via NuGet (Recommended)<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#via-nuget-recommended"></a></h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet tool install --global ElBruno.OllamaMonitor</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then launch anytime:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">ollamamon</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">From Source<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#from-source"></a></h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">git clone https://github.com/elbruno/ElBruno.OllamaMonitor.git</div><div class="cm-line">cd ElBruno.OllamaMonitor</div><div class="cm-line">dotnet build src/ElBruno.OllamaMonitor/</div><div class="cm-line">dotnet run --project src/ElBruno.OllamaMonitor/</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Quick Start<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#quick-start"></a></h2>



<ol class="wp-block-list">
<li><strong>Launch the app:</strong><code>ollamamon </code>The app starts minimized to the tray. Click the icon to open the details window or mini monitor.</li>



<li><strong>Check your status:</strong> Look at the tray icon color—it tells you Ollama’s status at a glance.</li>



<li><strong>Configure (optional):</strong> See <a href="https://elbruno.github.io/ElBruno.OllamaMonitor/docs/configuration.html">Configuration Guide</a> for endpoint, refresh rate, and threshold settings.</li>
</ol>



<h2 class="wp-block-heading">Features<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#features"></a></h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>System Tray Integration</strong> — Runs in the background, always visible</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Visual Status Indicators</strong> — Color-coded icons for quick status checks</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Standard Details Window</strong> — A normal Windows window with minimize/close behavior that keeps the app in the tray when closed</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Mini Monitor Window</strong> — A semi-transparent always-on-top compact view for CPU, RAM, GPU, and model status</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Local Configuration</strong> — Customize endpoint, refresh rate, thresholds</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>CLI Commands</strong> — Fully scriptable configuration</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>GPU Metrics</strong> — Best-effort NVIDIA GPU tracking (if nvidia-smi is available)</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Copy to Clipboard</strong> — Quickly share diagnostics</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Manual Refresh</strong> — Force an immediate check</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Open Ollama URL</strong> — Quick link to the Ollama API</li>
</ul>



<h2 class="wp-block-heading">Requirements<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#requirements"></a></h2>



<ul class="wp-block-list">
<li><strong>Windows 10 / Windows 11</strong> (requires .NET 10 runtime, which can be downloaded from <a href="https://dotnet.microsoft.com/">dotnet.microsoft.com</a>)</li>



<li><strong>Ollama</strong> running locally (download from <a href="https://ollama.ai/">ollama.ai</a>)</li>



<li><strong>.NET 10 SDK</strong> to build from source</li>
</ul>



<p class="wp-block-paragraph">Optional:</p>



<ul class="wp-block-list">
<li><strong>nvidia-smi</strong> (NVIDIA GPU drivers) for GPU metrics</li>
</ul>



<h2 class="wp-block-heading">Configuration<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/#configuration"></a></h2>



<p class="wp-block-paragraph">See&nbsp;<a href="https://elbruno.github.io/ElBruno.OllamaMonitor/docs/configuration.html">Configuration Guide</a>&nbsp;for detailed setup, CLI commands, custom thresholds, and advanced options like remote Ollama monitoring.</p>



<p class="wp-block-paragraph"><strong>Try it:</strong>&nbsp;<code>dotnet tool install --global ElBruno.OllamaMonitor</code></p>



<p class="wp-block-paragraph"><strong>Docs:</strong>&nbsp;See the&nbsp;<a href="https://github.com/ElBruno/ElBruno.OllamaMonitor">GitHub repository</a></p>



<p class="wp-block-paragraph"><strong>Made by:</strong>&nbsp;<a href="https://elbruno.com/">El Bruno</a>&nbsp;— A .NET developer obsessed with local AI and productivity.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/28/ollamamonitor-monitor-your-local-ollama-runtime-from-the-windows-tray/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40260</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif">
			<media:title type="html">ollamanitor-demo01</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/ollamanitor-demo01.gif?w=426"/>
	</item>
		<item>
		<title>AspireMonitor: Stop bouncing between your Aspire dashboard and your code.</title>
		<link>https://elbruno.com/2026/04/27/aspiremonitor-stop-bouncing-between-your-aspire-dashboard-and-your-code/</link>
					<comments>https://elbruno.com/2026/04/27/aspiremonitor-stop-bouncing-between-your-aspire-dashboard-and-your-code/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 27 Apr 2026 12:48:15 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Aspire]]></category>
		<category><![CDATA[CLI Tools]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40256</guid>

					<description><![CDATA[Hi! What it is AspireMonitor is a Windows tray app that puts your Aspire AppHost status one click away. No browser tabs. No switching context. Click the tray icon, see what&#8217;s running, Start/Stop your app, or pin the resources you actually care about in a compact mini window. Why it exists If you use Aspire, [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/blog-post.md#aspiremonitor-stop-bouncing-between-your-aspire-dashboard-and-your-code"></a></p>



<figure class="wp-block-image"><a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/screenshots/hero-banner.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/ElBruno.AspireMonitor/raw/main/docs/promotional/screenshots/hero-banner.png" alt="AspireMonitor — your Aspire AppHost, one tray icon away" /></a></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet tool install --global ElBruno.AspireMonitor
aspiremon
</pre></div>


<h2 class="wp-block-heading">What it is<a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/blog-post.md#what-it-is"></a></h2>



<p class="wp-block-paragraph">AspireMonitor is a Windows tray app that puts your Aspire AppHost status one click away. No browser tabs. No switching context. Click the tray icon, see what&#8217;s running, Start/Stop your app, or pin the resources you actually care about in a compact mini window.</p>



<h2 class="wp-block-heading">Why it exists<a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/blog-post.md#why-it-exists"></a></h2>



<p class="wp-block-paragraph">If you use Aspire, you know the dashboard is useful—but you don&#8217;t want a browser tab open all day. You&#8217;re writing code, not monitoring. AspireMonitor solves the friction: a lightweight tray icon that gives you &#8220;is my API up?&#8221; and &#8220;what URL does this service run on?&#8221; without leaving your IDE.</p>



<h2 class="wp-block-heading">Why it&#8217;s useful<a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/blog-post.md#why-its-useful"></a></h2>



<p class="wp-block-paragraph"><strong>v1.4.0 ships with two killer features:</strong></p>



<p class="wp-block-paragraph">The mini window pins only the resources you care about (configure once:&nbsp;<code>web, store, gateway</code>) with their live URLs and Start/Stop buttons. The main window shows the full resource list when you need it.</p>



<p class="wp-block-paragraph">Start now works correctly—it stays disabled with&nbsp;<code>&#x23f3; Starting Aspire... (12 / 90s)</code>&nbsp;so you know when resources are actually ready. Stop actually stops. URLs are real (<code>http://localhost:5021</code>), not generic &#8220;Open&#8221; links.</p>



<figure class="wp-block-image"><a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/screenshots/mini-window.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/ElBruno.AspireMonitor/raw/main/docs/promotional/screenshots/mini-window.png" alt="AspireMonitor mini window—pinned resources with live URLs" /></a></figure>



<figure class="wp-block-image"><a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/screenshots/main-window.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/ElBruno.AspireMonitor/raw/main/docs/promotional/screenshots/main-window.png" alt="AspireMonitor main window—full resource list" /></a></figure>



<h2 class="wp-block-heading">Get it<a href="https://github.com/elbruno/ElBruno.AspireMonitor/blob/main/docs/promotional/blog-post.md#get-it"></a></h2>



<ul class="wp-block-list">
<li><strong>NuGet:</strong> <a href="https://www.nuget.org/packages/ElBruno.AspireMonitor">https://www.nuget.org/packages/ElBruno.AspireMonitor</a></li>



<li><strong>GitHub:</strong> <a href="https://github.com/elbruno/ElBruno.AspireMonitor">https://github.com/elbruno/ElBruno.AspireMonitor</a></li>



<li><strong>MIT licensed.</strong> Issues and PRs welcome.</li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Built with .NET 10 + WPF. Shells out to&nbsp;<code>aspire describe</code>—no third-party SDK dependency.</p>
</blockquote>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/27/aspiremonitor-stop-bouncing-between-your-aspire-dashboard-and-your-code/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40256</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/ElBruno.AspireMonitor/raw/main/docs/promotional/screenshots/hero-banner.png">
			<media:title type="html">AspireMonitor — your Aspire AppHost, one tray icon away</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/ElBruno.AspireMonitor/raw/main/docs/promotional/screenshots/mini-window.png">
			<media:title type="html">AspireMonitor mini window—pinned resources with live URLs</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/ElBruno.AspireMonitor/raw/main/docs/promotional/screenshots/main-window.png">
			<media:title type="html">AspireMonitor main window—full resource list</media:title>
		</media:content>
	</item>
		<item>
		<title>&#128347; ClockTray v1.0.0: Meet the CLI-Enabled Clock Tool for Windows</title>
		<link>https://elbruno.com/2026/04/23/%f0%9f%95%9b-clocktray-v1-0-0-meet-the-cli-enabled-clock-tool-for-windows/</link>
					<comments>https://elbruno.com/2026/04/23/%f0%9f%95%9b-clocktray-v1-0-0-meet-the-cli-enabled-clock-tool-for-windows/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 23 Apr 2026 11:01:33 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[CLI Tools]]></category>
		<category><![CDATA[ClockTray]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[technology]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40251</guid>

					<description><![CDATA[Hi! usually hide my taskbar clock while recording videos, and after doing that dance one too many times, I had one goal:&#160;save a few clicks. Then yesterday, during a GitHub Copilot CLI session (watch it here), I decided to pick up the ClockTray app and turn it into a CLI tool. And&#8230; here we are. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="512" height="512" src="https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png?w=512" alt="" class="wp-image-40253" srcset="https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png 512w, https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png?w=300 300w" sizes="(max-width: 512px) 100vw, 512px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">usually hide my taskbar clock while recording videos, and after doing that dance one too many times, I had one goal:&nbsp;<strong>save a few clicks</strong>.</p>



<p class="wp-block-paragraph">Then yesterday, during a GitHub Copilot CLI session (<a href="https://www.youtube.com/watch?v=uVTgs4WieY4">watch it here</a>), I decided to pick up the ClockTray app and turn it into a CLI tool.</p>



<p class="wp-block-paragraph">And&#8230; here we are. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 class="wp-block-heading">Introducing the ClockTray CLI </h2>



<p class="wp-block-paragraph">ClockTray v1.0.0 is now a <strong>dual-mode utility</strong> combining a user-friendly GUI with powerful command-line controls.</p>



<p class="wp-block-paragraph">The headline feature?&nbsp;<strong>Full CLI support.</strong>&nbsp;You can now control your taskbar clock entirely from PowerShell, batch scripts, or any automation tool. Show it, hide it, check its status—all without touching the mouse.</p>



<h2 class="wp-block-heading">The New CLI Commands</h2>



<p class="wp-block-paragraph">Here&#8217;s what you can do right from your terminal:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Show the clock on your taskbar</div><div class="cm-line">clocktray show</div><div class="cm-line"></div><div class="cm-line"># Hide the clock</div><div class="cm-line">clocktray hide</div><div class="cm-line"></div><div class="cm-line"># Check the current status</div><div class="cm-line">clocktray status</div><div class="cm-line"></div><div class="cm-line"># View all available commands</div><div class="cm-line">clocktray --help</div><div class="cm-line"></div><div class="cm-line"># Check the version</div><div class="cm-line">clocktray --version</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Each command is instant, non-blocking, and integrates seamlessly with your scripts and workflows.</p>



<h2 class="wp-block-heading">One Tool, Two Modes</h2>



<p class="wp-block-paragraph">ClockTray now works the way&nbsp;<em>you</em>&nbsp;work:</p>



<p class="wp-block-paragraph"><strong>GUI Mode:</strong>&nbsp;Launch the application normally for a traditional system tray experience. Right-click the icon to toggle your clock or access settings—perfect for quick manual control.</p>



<p class="wp-block-paragraph"><strong>CLI Mode:</strong>&nbsp;Run&nbsp;<code>clocktray</code>&nbsp;commands from PowerShell, Windows Terminal, or your build scripts. Perfect for automation, CI/CD pipelines, and repetitive tasks.</p>



<p class="wp-block-paragraph">You don&#8217;t need separate tools. You get both, in a single 30-second install.</p>



<h2 class="wp-block-heading">Automation &amp; Real-World Use Cases</h2>



<p class="wp-block-paragraph">Imagine these scenarios—now all possible with ClockTray:</p>



<ul class="wp-block-list">
<li><strong>Streaming Setup:</strong> Hide the clock before you go live, show it again when you stop.</li>



<li><strong>Multi-Monitor Management:</strong> Toggle the clock based on which display you&#8217;re using—via a custom PowerShell profile.</li>



<li><strong>Build Pipelines:</strong> Hide the clock during automated testing, restore it when complete.</li>



<li><strong>Accessibility Workflows:</strong> Create quick-access scripts that adapt your desktop to different needs.</li>



<li><strong>Time-Tracking Scripts:</strong> Check the clock status as part of a larger automation routine.</li>
</ul>



<p class="wp-block-paragraph">PowerShell scripters and DevOps engineers, you&#8217;re going to love this.</p>



<h2 class="wp-block-heading">Bonus Feature: Chinese Lunar Calendar Overlay</h2>



<p class="wp-block-paragraph">ClockTray also includes a beautiful overlay showing the Chinese lunar calendar—perfect if you&#8217;re tracking traditional holidays, working with international teams, or simply interested in lunar dates. This overlay displays alongside your taskbar clock and respects your show/hide commands.</p>



<h2 class="wp-block-heading">Installation in 30 Seconds</h2>



<p class="wp-block-paragraph">You can install ClockTray as a global .NET tool from NuGet:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet tool install --global ElBruno.ClockTray</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it. You&#8217;ll have&nbsp;<code>clocktray</code>&nbsp;available in any PowerShell session or terminal immediately.</p>



<p class="wp-block-paragraph">Update an existing installation? Use:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet tool update --global ElBruno.ClockTray</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Get ClockTray Today</h2>



<ul class="wp-block-list">
<li><strong>GitHub:</strong> <a href="https://github.com/elbruno/ElBruno.ClockTray">github.com/elbruno/ElBruno.ClockTray</a></li>



<li><strong>NuGet:</strong> <a href="https://www.nuget.org/packages/ElBruno.ClockTray">nuget.org/packages/ElBruno.ClockTray</a></li>
</ul>



<p class="wp-block-paragraph">Explore the source code, contribute features, or report issues on GitHub. The project is open-source and welcomes your feedback.</p>



<h2 class="wp-block-heading">What&#8217;s Next</h2>



<p class="wp-block-paragraph">With v1.0.0 released, we&#8217;re already thinking ahead. Future updates may include:</p>



<ul class="wp-block-list">
<li>Configuration files for default behavior</li>



<li>Extended automation scenarios</li>



<li>Cross-platform considerations</li>
</ul>



<p class="wp-block-paragraph">Your input shapes this tool. If you have ideas, open an issue or discussion on GitHub.</p>



<h2 class="wp-block-heading">Try It Now</h2>



<p class="wp-block-paragraph">Whether you&#8217;re a PowerShell enthusiast, a DevOps engineer, or someone who just wants better desktop control, ClockTray v1.0.0 has something for you. Install it today, explore the CLI, and tell us what you build.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/23/%f0%9f%95%9b-clocktray-v1-0-0-meet-the-cli-enabled-clock-tool-for-windows/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40251</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png">
			<media:title type="html">clocktray-cli-small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/clocktray-cli-small.png?w=512"/>
	</item>
		<item>
		<title>&#128640; Big Update: GPT-Image Models + AI Agent Skills</title>
		<link>https://elbruno.com/2026/04/22/%f0%9f%9a%80-big-update-gpt-image-models-ai-agent-skills/</link>
					<comments>https://elbruno.com/2026/04/22/%f0%9f%9a%80-big-update-gpt-image-models-ai-agent-skills/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 22 Apr 2026 12:06:09 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[CLI Tools]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[NuGet Packages]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40242</guid>

					<description><![CDATA[Hi! Two weeks ago, I&#160;shipped&#160;t2i&#160;— a terminal-first CLI for text-to-image generation. Today I&#8217;m excited to announce&#160;two major additions&#160;that make&#160;t2i&#160;even more powerful: TL;DR 🤖 Part 1: AI Agent Skills — The Biggest Feature This is the feature I&#8217;m most excited about: teaching AI agents how to use t2i automatically What Are Skills? Skills are packages of functionality that AI [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/blogs/20260422-gpt-images-and-skills.md#-big-update-gpt-image-models--ai-agent-skills"></a></p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Two weeks ago, I&nbsp;<a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.Text2Image/docs/blogs/20260420-introducing-t2i-cli.md">shipped&nbsp;<code>t2i</code></a>&nbsp;— a terminal-first CLI for text-to-image generation. Today I&#8217;m excited to announce&nbsp;<strong>two major additions</strong>&nbsp;that make&nbsp;<code>t2i</code>&nbsp;even more powerful:</p>



<ol class="wp-block-list">
<li><strong>GPT-Image-1.5 and GPT-Image-2 support</strong> — Microsoft&#8217;s DALL-E 3 and next-gen models via Azure OpenAI</li>



<li><strong>AI agent skill integration</strong> — Teach GitHub Copilot and Claude Code to use <code>t2i</code> automatically</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">TL;DR</h2>



<ul class="wp-block-list">
<li><strong>New models:</strong> GPT-Image-1.5 (DALL-E 3) and GPT-Image-2 (next-gen) now available via Azure OpenAI</li>



<li><strong>Skill integration:</strong> Run <code>t2i init</code> to teach GitHub Copilot and Claude Code how to generate images autonomously</li>



<li><strong>Updated providers:</strong> Now supporting 5 cloud models (FLUX.2 Pro, FLUX.2 Flex, MAI-Image-2, GPT-Image-1.5, GPT-Image-2)</li>



<li><strong>Version:</strong> Available in <code>t2i</code> v0.16.0+ via <code>dotnet tool update --global ElBruno.Text2Image.Cli</code></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Part 1: AI Agent Skills — The Biggest Feature</h2>



<p class="wp-block-paragraph">This is the feature I&#8217;m most excited about: <strong>teaching AI agents how to use <code>t2i</code> automatically</strong></p>



<h3 class="wp-block-heading">What Are Skills?</h3>



<p class="wp-block-paragraph">Skills are packages of functionality that AI coding agents can discover and invoke on their own. By installing a skill file, you enable agents like GitHub Copilot and Claude Code to:</p>



<ul class="wp-block-list">
<li><strong>Generate images</strong> directly within your development workflow</li>



<li><strong>Automate batch creation</strong> based on natural language requests</li>



<li><strong>Integrate image generation</strong> into CI/CD pipelines and automation scripts</li>
</ul>



<p class="wp-block-paragraph">Skills work by placing metadata files in well-known directories (<code>.github/skills/</code>&nbsp;for Copilot,&nbsp;<code>.claude/skills/</code>&nbsp;for Claude Code) that agents scan during initialization. Once installed, these agents understand:</p>



<ul class="wp-block-list">
<li>Which <code>t2i</code> commands exist and when to use each one</li>



<li>How to set up secrets safely (env vars first, never commit keys)</li>



<li>The full provider list and which one to default to</li>



<li>Common workflows: first-time setup, single image, batch loops</li>
</ul>



<h3 class="wp-block-heading">How to Set It Up</h3>



<p class="wp-block-paragraph">Install the cli tool locally, setup the tool and from any repository:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i init</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it. This command writes skill metadata to:</p>



<ul class="wp-block-list">
<li><code>.github/skills/t2i/SKILL.md</code> (for GitHub Copilot)</li>



<li><code>.claude/skills/t2i/SKILL.md</code> (for Claude Code)</li>
</ul>



<p class="wp-block-paragraph">Want only one target?</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i init --target github   # GitHub Copilot only</div><div class="cm-line">t2i init --target claude   # Claude Code only</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The skill files include:</p>



<ul class="wp-block-list">
<li>Tool overview and capabilities</li>



<li>Command syntax with examples</li>



<li>Provider configuration instructions</li>



<li>Best practices and troubleshooting tips</li>
</ul>



<h3 class="wp-block-heading">Real-World Example: GitHub Copilot</h3>



<p class="wp-block-paragraph">After running&nbsp;<code>t2i init --target github</code>, you can interact with Copilot naturally:</p>



<p class="wp-block-paragraph"><strong>You:</strong>&nbsp;&#8220;Generate a futuristic cityscape with neon lights and save it as hero.png&#8221;</p>



<p class="wp-block-paragraph"><strong>Copilot:</strong>&nbsp;<em>Automatically invokes:</em></p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;futuristic cityscape with neon lights, cyberpunk style, volumetric fog&quot; \</div><div class="cm-line">  --provider foundry-flux2 \</div><div class="cm-line">  --width 1792 \</div><div class="cm-line">  --height 1024 \</div><div class="cm-line">  --output hero.png</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><strong>You:</strong>&nbsp;&#8220;Create a series of social media images for our product launch — abstract tech theme&#8221;</p>



<p class="wp-block-paragraph"><strong>Copilot:</strong>&nbsp;<em>Automatically invokes:</em></p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;abstract tech background with circuit patterns&quot; --output social-1.png</div><div class="cm-line">t2i &quot;geometric tech shapes with gradient colors&quot; --output social-2.png</div><div class="cm-line">t2i &quot;digital network visualization, modern style&quot; --output social-3.png</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">No need to remember the exact syntax or provider flags — Copilot handles it.</p>



<h3 class="wp-block-heading">Real-World Example: Claude Code</h3>



<p class="wp-block-paragraph">After running&nbsp;<code>t2i init --target claude</code>, Claude can drive&nbsp;<code>t2i</code>&nbsp;based on your requests:</p>



<p class="wp-block-paragraph"><strong>You:</strong>&nbsp;&#8220;I need an image of a sunset over mountains for the landing page, wide format&#8221;</p>



<p class="wp-block-paragraph"><strong>Claude:</strong>&nbsp;<em>Automatically invokes:</em></p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;sunset over mountains, warm golden hour colors, panoramic view&quot; \</div><div class="cm-line">  --provider foundry-flux2 \</div><div class="cm-line">  --width 1792 \</div><div class="cm-line">  --height 1024 \</div><div class="cm-line">  --output landing-hero.png</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><strong>You:</strong>&nbsp;&#8220;Generate icon placeholders: home, settings, profile — all square, simple line art&#8221;</p>



<p class="wp-block-paragraph"><strong>Claude:</strong>&nbsp;<em>Automatically invokes multiple commands:</em></p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;home icon, simple line art, minimalist, 512x512&quot; --out icon-home.png</div><div class="cm-line">t2i &quot;settings icon, simple line art, minimalist, 512x512&quot; --out icon-settings.png</div><div class="cm-line">t2i &quot;profile icon, simple line art, minimalist, 512x512&quot; --out icon-profile.png</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Why This Matters</h3>



<p class="wp-block-paragraph">Before skills, you had to:</p>



<ol class="wp-block-list">
<li>Remember <code>t2i</code> syntax</li>



<li>Look up provider names</li>



<li>Check configuration flags</li>



<li>Write your own automation scripts</li>
</ol>



<p class="wp-block-paragraph">With skills installed, AI agents become your&nbsp;<strong>image generation assistant</strong>:</p>



<ul class="wp-block-list">
<li>They know the syntax</li>



<li>They pick the right provider</li>



<li>They handle dimensions and output paths</li>



<li>They batch generate when appropriate</li>
</ul>



<p class="wp-block-paragraph">This is especially powerful in CI/CD scenarios. Imagine a GitHub Actions workflow where Copilot autonomously generates marketing assets, social media images, or documentation screenshots based on a simple prompt list.</p>



<p class="wp-block-paragraph"><strong>Configure providers first</strong></p>



<p class="wp-block-paragraph">Before your AI agent can use&nbsp;<code>t2i</code>, ensure at least one provider is configured:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i config   # Interactive setup</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><strong>Use environment variables in CI/CD</strong></p>



<p class="wp-block-paragraph">For automated workflows, configure via env vars:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># GitHub Actions example</div><div class="cm-line">- name: Generate images</div><div class="cm-line">  env:</div><div class="cm-line">    T2I_FOUNDRY_FLUX2_ENDPOINT: ${{ secrets.FOUNDRY_ENDPOINT }}</div><div class="cm-line">    T2I_FOUNDRY_FLUX2_APIKEY: ${{ secrets.FOUNDRY_APIKEY }}</div><div class="cm-line">  run: |</div><div class="cm-line">    dotnet tool install --global ElBruno.Text2Image.Cli</div><div class="cm-line">    t2i init</div><div class="cm-line">    t2i &quot;hero image for landing page&quot; --provider foundry-flux2 --out assets/hero.png</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><strong>Update skills after CLI upgrades</strong></p>



<p class="wp-block-paragraph">When you update&nbsp;<code>t2i</code>, refresh the skill metadata:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet tool update --global ElBruno.Text2Image.Cli</div><div class="cm-line">t2i init   # Regenerates skill files with latest docs</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">More Details</h3>



<p class="wp-block-paragraph">For the complete skill integration guide, including troubleshooting, advanced customization, and platform-specific instructions, see:</p>



<p class="wp-block-paragraph"><strong>→&nbsp;<a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.Text2Image/docs/skill-integration.md">docs/skill-integration.md</a></strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Part 2: GPT-Image Models — More Choices</h2>



<p class="wp-block-paragraph">Now for the second big update:&nbsp;<strong>GPT-Image-1.5 and GPT-Image-2 support</strong>.</p>



<p class="wp-block-paragraph">These are Microsoft&#8217;s image generation models available via Azure OpenAI Service. Both are based on OpenAI technology but deployed in Azure for enterprise-grade reliability, compliance, and control.</p>



<h3 class="wp-block-heading">GPT-Image-1.5 (DALL-E 3)</h3>



<p class="wp-block-paragraph"><strong>What it is:</strong>&nbsp;Azure OpenAI&#8217;s implementation of OpenAI&#8217;s DALL-E 3 model.</p>



<p class="wp-block-paragraph"><strong>Best for:</strong></p>



<ul class="wp-block-list">
<li><strong>Natural language prompts</strong> — Excellent at understanding complex, conversational descriptions</li>



<li><strong>Photorealistic images</strong> — Great for realistic scenes, portraits, and product photography</li>



<li><strong>Text rendering</strong> — Better at including readable text in images (though still not perfect)</li>



<li><strong>Enterprise compliance</strong> — Deployed in your Azure region with full data residency</li>
</ul>



<p class="wp-block-paragraph"><strong>Supported sizes:</strong></p>



<ul class="wp-block-list">
<li>1024×1024 (square)</li>



<li>1792×1024 (landscape)</li>



<li>1024×1792 (portrait)</li>
</ul>



<p class="wp-block-paragraph"><strong>Example use cases:</strong></p>



<ul class="wp-block-list">
<li>Marketing visuals with text overlays</li>



<li>Product mockups and packaging designs</li>



<li>Editorial illustrations for blog posts</li>



<li>Social media graphics</li>
</ul>



<h3 class="wp-block-heading">GPT-Image-2 (Next-Gen)</h3>



<p class="wp-block-paragraph"><strong>What it is:</strong>&nbsp;Microsoft&#8217;s next-generation image model — more advanced than DALL-E 3.</p>



<p class="wp-block-paragraph"><strong>Best for:</strong></p>



<ul class="wp-block-list">
<li><strong>High-quality artistic images</strong> — Improved coherence and style consistency</li>



<li><strong>Complex compositions</strong> — Better at multi-object scenes with detailed relationships</li>



<li><strong>Stylized rendering</strong> — Excels at specific art styles (watercolor, oil painting, digital art)</li>



<li><strong>Prompt adherence</strong> — Follows instructions more accurately, especially for abstract concepts</li>
</ul>



<p class="wp-block-paragraph"><strong>Supported sizes:</strong></p>



<ul class="wp-block-list">
<li>1024×1024 (square)</li>



<li>1792×1024 (landscape)</li>



<li>1024×1792 (portrait)</li>
</ul>



<p class="wp-block-paragraph"><strong>Example use cases:</strong></p>



<ul class="wp-block-list">
<li>Concept art and game design</li>



<li>Artistic book cover designs</li>



<li>Abstract and stylized illustrations</li>



<li>Character design and visual development</li>
</ul>



<h3 class="wp-block-heading">How to Use Them</h3>



<p class="wp-block-paragraph">Both models use the&nbsp;<strong>Azure OpenAI Service</strong>, so you need:</p>



<ol class="wp-block-list">
<li>An Azure subscription</li>



<li>An Azure OpenAI resource</li>



<li>A deployment of <code>gpt-image-1.5</code> or <code>gpt-image-2</code></li>



<li>Your endpoint URL and API key</li>
</ol>



<h4 class="wp-block-heading">Quick Setup (GPT-Image-1.5)</h4>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Interactive setup wizard</div><div class="cm-line">t2i config set foundry-gpt-image-1p5</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The wizard prompts for:</p>



<ul class="wp-block-list">
<li><strong>Endpoint URL</strong> (e.g., <code>https://my-resource.openai.azure.com/</code>)</li>



<li><strong>API Key</strong> (from Azure Portal)</li>



<li><strong>Deployment name</strong> (e.g., <code>gpt-image-15</code>)</li>
</ul>



<p class="wp-block-paragraph">Then generate:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;an impressionist painting of a garden in spring&quot; \</div><div class="cm-line">  --provider foundry-gpt-image-1p5</div></code></pre>
		</div>
	</div>
</div>


<h4 class="wp-block-heading">Quick Setup (GPT-Image-2)</h4>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Interactive setup wizard</div><div class="cm-line">t2i config set foundry-gpt-image-2</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then generate:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;a sci-fi space station orbiting a ringed planet, digital art&quot; \</div><div class="cm-line">  --provider foundry-gpt-image-2</div></code></pre>
		</div>
	</div>
</div>


<h4 class="wp-block-heading">PowerShell Examples</h4>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># GPT-Image-1.5: Photorealistic product shot</div><div class="cm-line">t2i &quot;professional product photo of a smartwatch on marble, studio lighting&quot; `</div><div class="cm-line">  --provider foundry-gpt-image-1p5 `</div><div class="cm-line">  --width 1792 `</div><div class="cm-line">  --height 1024 `</div><div class="cm-line">  --output product-hero.png</div><div class="cm-line"></div><div class="cm-line"># GPT-Image-2: Abstract art for website header</div><div class="cm-line">t2i &quot;abstract geometric patterns with vibrant gradients, modern tech aesthetic&quot; `</div><div class="cm-line">  --provider foundry-gpt-image-2 `</div><div class="cm-line">  --output header-bg.png</div></code></pre>
		</div>
	</div>
</div>


<h4 class="wp-block-heading">Bash Examples</h4>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># GPT-Image-1.5: Editorial illustration</div><div class="cm-line">t2i &quot;a serene lake at sunrise with mountains in the distance, photorealistic&quot; \</div><div class="cm-line">  --provider foundry-gpt-image-1p5 \</div><div class="cm-line">  --width 1792 \</div><div class="cm-line">  --height 1024 \</div><div class="cm-line">  --output editorial.png</div><div class="cm-line"></div><div class="cm-line"># GPT-Image-2: Character concept art</div><div class="cm-line">t2i &quot;character concept art of a futuristic knight, armor with neon accents, digital painting&quot; \</div><div class="cm-line">  --provider foundry-gpt-image-2 \</div><div class="cm-line">  --width 1024 \</div><div class="cm-line">  --height 1792 \</div><div class="cm-line">  --output character.png</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Switching Models</h3>



<p class="wp-block-paragraph">You can set a default provider in your config:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Use GPT-Image-2 as default</div><div class="cm-line">t2i config set default-provider foundry-gpt-image-2</div><div class="cm-line"></div><div class="cm-line"># Now this uses GPT-Image-2</div><div class="cm-line">t2i &quot;your prompt here&quot;</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Or specify per-command:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Compare outputs from different models</div><div class="cm-line">t2i &quot;a cyberpunk cityscape&quot; --provider foundry-flux2 --output flux-city.png</div><div class="cm-line">t2i &quot;a cyberpunk cityscape&quot; --provider foundry-gpt-image-2 --output gpt-city.png</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Complete Provider List</h3>



<p class="wp-block-paragraph">Here&#8217;s the full lineup after this update:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Provider ID</th><th class="has-text-align-left" data-align="left">Model</th><th class="has-text-align-left" data-align="left">Provider</th><th class="has-text-align-left" data-align="left">Best For</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><code>foundry-flux2</code></td><td class="has-text-align-left" data-align="left">FLUX.2 Pro</td><td class="has-text-align-left" data-align="left">Microsoft Foundry</td><td class="has-text-align-left" data-align="left">Photorealistic images, fine control</td></tr><tr><td class="has-text-align-left" data-align="left"><code>foundry-flux2</code>&nbsp;(Flex)</td><td class="has-text-align-left" data-align="left">FLUX.2 Flex</td><td class="has-text-align-left" data-align="left">Microsoft Foundry</td><td class="has-text-align-left" data-align="left">Text-heavy designs, logos</td></tr><tr><td class="has-text-align-left" data-align="left"><code>foundry-mai2</code></td><td class="has-text-align-left" data-align="left">MAI-Image-2</td><td class="has-text-align-left" data-align="left">Microsoft Foundry</td><td class="has-text-align-left" data-align="left">Fast iteration, rich prompts</td></tr><tr><td class="has-text-align-left" data-align="left"><code>foundry-gpt-image-1p5</code></td><td class="has-text-align-left" data-align="left">GPT-Image-1.5</td><td class="has-text-align-left" data-align="left">Azure OpenAI</td><td class="has-text-align-left" data-align="left">Natural language, photorealism</td></tr><tr><td class="has-text-align-left" data-align="left"><code>foundry-gpt-image-2</code></td><td class="has-text-align-left" data-align="left">GPT-Image-2</td><td class="has-text-align-left" data-align="left">Azure OpenAI</td><td class="has-text-align-left" data-align="left">Next-gen quality, style consistency</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>How to choose:</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Model Comparison</h2>



<p class="wp-block-paragraph">Here&#8217;s a quick comparison to help you choose:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Feature</th><th class="has-text-align-left" data-align="left">FLUX.2 Pro</th><th class="has-text-align-left" data-align="left">MAI-Image-2</th><th class="has-text-align-left" data-align="left">GPT-Image-1.5</th><th class="has-text-align-left" data-align="left">GPT-Image-2</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><strong>Quality</strong></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Speed</strong></td><td class="has-text-align-left" data-align="left">~30-40s</td><td class="has-text-align-left" data-align="left">~15-20s</td><td class="has-text-align-left" data-align="left">~8-12s</td><td class="has-text-align-left" data-align="left">~8-12s</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Photorealism</strong></td><td class="has-text-align-left" data-align="left">Excellent</td><td class="has-text-align-left" data-align="left">Good</td><td class="has-text-align-left" data-align="left">Excellent</td><td class="has-text-align-left" data-align="left">Excellent</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Artistic Styles</strong></td><td class="has-text-align-left" data-align="left">Good</td><td class="has-text-align-left" data-align="left">Excellent</td><td class="has-text-align-left" data-align="left">Good</td><td class="has-text-align-left" data-align="left">Excellent</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Text Rendering</strong></td><td class="has-text-align-left" data-align="left">Poor</td><td class="has-text-align-left" data-align="left">Fair</td><td class="has-text-align-left" data-align="left">Good</td><td class="has-text-align-left" data-align="left">Good</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Prompt Adherence</strong></td><td class="has-text-align-left" data-align="left">Excellent</td><td class="has-text-align-left" data-align="left">Very Good</td><td class="has-text-align-left" data-align="left">Very Good</td><td class="has-text-align-left" data-align="left">Excellent</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Custom Sizes</strong></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Any size</td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Any size</td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed sizes</td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Fixed sizes</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>API Type</strong></td><td class="has-text-align-left" data-align="left">Async (polling)</td><td class="has-text-align-left" data-align="left">Async (polling)</td><td class="has-text-align-left" data-align="left">Sync</td><td class="has-text-align-left" data-align="left">Sync</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Provider</strong></td><td class="has-text-align-left" data-align="left">Microsoft Foundry</td><td class="has-text-align-left" data-align="left">Microsoft Foundry</td><td class="has-text-align-left" data-align="left">Azure OpenAI</td><td class="has-text-align-left" data-align="left">Azure OpenAI</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>Performance notes:</strong></p>



<ul class="wp-block-list">
<li>FLUX.2 and MAI models use asynchronous polling (submit → poll → retrieve)</li>



<li>GPT-Image models use synchronous API (submit → wait → receive)</li>



<li>First request may be slower due to model warm-up</li>



<li>Batch jobs benefit from parallel requests</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Migration Guide</h2>



<p class="wp-block-paragraph">If you&#8217;re already using&nbsp;<code>t2i</code>&nbsp;with FLUX.2 or MAI-Image-2, upgrading is straightforward.</p>



<h3 class="wp-block-heading">Step 1: Update the CLI</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet tool update --global ElBruno.Text2Image.Cli</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Verify:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i --version</div><div class="cm-line"># Should show v0.16.0 or later</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Step 2: List New Providers</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i providers</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">You should see:</p>



<ul class="wp-block-list">
<li><code>foundry-gpt-image-1p5</code> — GPT-Image-1.5 / DALL-E 3</li>



<li><code>foundry-gpt-image-2</code> — GPT-Image-2 (Next-Gen)</li>
</ul>



<h3 class="wp-block-heading">Step 3: Configure GPT Models (Optional)</h3>



<p class="wp-block-paragraph">If you want to use GPT-Image models:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i config set foundry-gpt-image-1p5   # Configure GPT-Image-1.5</div><div class="cm-line">t2i config set foundry-gpt-image-2     # Configure GPT-Image-2</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Step 4: Update Skill Files</h3>



<p class="wp-block-paragraph">If you previously ran&nbsp;<code>t2i init</code>, refresh your skill metadata:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i init --force   # Overwrites existing skill files</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This ensures GitHub Copilot and Claude Code know about the new models.</p>



<h3 class="wp-block-heading">Step 5: Test</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i doctor</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Check that all providers show as &#8220;configured&#8221; and &#8220;healthy.&#8221;</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> What&#8217;s Next</h2>



<p class="wp-block-paragraph">This release adds major capabilities, but there&#8217;s more coming:</p>



<p class="wp-block-paragraph"><strong>v0.17.0+ (Q2 2026):</strong></p>



<ul class="wp-block-list">
<li><strong>Local inference edition</strong> — CPU, CUDA, DirectML providers (no cloud required)</li>



<li><strong>Model marketplace</strong> — Download and manage local ONNX models</li>



<li><strong>Batch API</strong> — Submit multiple prompts in one call</li>



<li><strong>Image-to-image</strong> — Use existing images as input for variations</li>
</ul>



<p class="wp-block-paragraph"><strong>Community-requested features:</strong></p>



<ul class="wp-block-list">
<li><strong>Negative prompts</strong> — Specify what <em>not</em> to include</li>



<li><strong>Style presets</strong> — Quick templates (e.g., <code>--style cinematic</code>)</li>



<li><strong>Config profiles</strong> — Switch between dev/prod configurations</li>



<li><strong>Web UI</strong> — Optional browser-based interface</li>
</ul>



<p class="wp-block-paragraph">Want to influence the roadmap? File a feature request:</p>



<p class="wp-block-paragraph"><strong>→&nbsp;<a href="https://github.com/elbruno/ElBruno.Text2Image/issues">github.com/elbruno/ElBruno.Text2Image/issues</a></strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Links &amp; Resources</h2>



<p class="wp-block-paragraph"><strong>Documentation:</strong></p>



<ul class="wp-block-list">
<li><a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.Text2Image/docs/cli-tool.md">CLI Tool Reference</a></li>



<li><a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.Text2Image/docs/skill-integration.md">Skill Integration Guide</a></li>



<li><a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.Text2Image/docs/gpt-image-1p5-setup-guide.md">GPT-Image-1.5 Setup Guide</a></li>
</ul>



<p class="wp-block-paragraph"><strong>Installation:</strong></p>



<ul class="wp-block-list">
<li><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli">NuGet Package</a></li>



<li><a href="https://github.com/elbruno/ElBruno.Text2Image/releases">GitHub Releases</a></li>
</ul>



<p class="wp-block-paragraph"><strong>Community:</strong></p>



<ul class="wp-block-list">
<li><a href="https://github.com/elbruno/ElBruno.Text2Image">GitHub Repository</a></li>



<li><a href="https://github.com/elbruno/ElBruno.Text2Image/issues">Report Issues</a></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph"><strong>Model:</strong>&nbsp;GPT-Image-2<br /><strong>Size:</strong>&nbsp;1792×1024 (landscape)<br /><strong>Generation time:</strong>&nbsp;~9.2 seconds<br /><strong>Cost:</strong>&nbsp;~$0.15</p>



<p class="wp-block-paragraph">This demonstrates GPT-Image-2&#8217;s ability to understand abstract concepts (a terminal &#8220;emitting&#8221; images) and render them with artistic style.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph">Questions? Feature requests? Found a bug?</p>



<p class="wp-block-paragraph"><strong>→&nbsp;<a href="https://github.com/elbruno/ElBruno.Text2Image/issues">github.com/elbruno/ElBruno.Text2Image/issues</a></strong></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/22/%f0%9f%9a%80-big-update-gpt-image-models-ai-agent-skills/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40242</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-26-2026-01_13_39-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-26-2026-01_13_39-pm.png">
			<media:title type="html">ChatGPT Image Feb 26, 2026, 01_13_39 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#128640; Meet t2i — The ElBruno.Text2Image CLI</title>
		<link>https://elbruno.com/2026/04/20/%f0%9f%9a%80-meet-t2i-the-elbruno-text2image-cli/</link>
					<comments>https://elbruno.com/2026/04/20/%f0%9f%9a%80-meet-t2i-the-elbruno-text2image-cli/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 20 Apr 2026 15:40:04 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[CLI Tools]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[Skills]]></category>
		<category><![CDATA[Text-To-Image]]></category>
		<category><![CDATA[Tools]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40237</guid>

					<description><![CDATA[Hi! I just shipped t2i, a terminal-first CLI tool for ElBruno.Text2Image. Generate images from your shell in two commands — no UI, no browser, just a simple cli interface to image generation from the cloud. This is the&#160;Lite edition&#160;(cloud-only, ~2.4 MB on NuGet) — perfect for CI/CD pipelines, deployment scripts, batch jobs, and developers who live [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="640" height="384" src="https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png?w=640" alt="" class="wp-image-40239" srcset="https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png 640w, https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png?w=300 300w" sizes="(max-width: 640px) 100vw, 640px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">I just shipped <strong>t2i</strong>, a terminal-first CLI tool for ElBruno.Text2Image. Generate images from your shell in two commands — no UI, no browser, just a simple cli interface to image generation from the cloud.</p>



<p class="wp-block-paragraph">This is the&nbsp;<strong>Lite edition</strong>&nbsp;(cloud-only, ~2.4 MB on NuGet) — perfect for CI/CD pipelines, deployment scripts, batch jobs, and developers who live in the terminal.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Install</h2>



<h3 class="wp-block-heading">Option 1: .NET Tool (recommended)</h3>



<p class="wp-block-paragraph">If you have .NET 8+ installed:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet tool install --global ElBruno.Text2Image.Cli</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then use&nbsp;<code>t2i</code>&nbsp;from anywhere on your machine.</p>



<h3 class="wp-block-heading">Option 2: Self-Contained Binaries</h3>



<p class="wp-block-paragraph">No .NET? Download pre-built binaries from the&nbsp;<a href="https://github.com/elbruno/ElBruno.Text2Image/releases/tag/cli-v0.10.0">release page</a>. Currently available for:</p>



<ul class="wp-block-list">
<li><strong>Windows x64</strong></li>



<li><strong>Linux x64</strong></li>



<li><strong>macOS arm64</strong> (Intel coming back soon)</li>
</ul>



<p class="wp-block-paragraph">Just extract and run. One file, no dependencies.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> First Image in 30 Seconds</h2>



<p class="wp-block-paragraph">Ready to generate? Two commands:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i config</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This launches an interactive setup wizard (powered by&nbsp;<a href="https://spectreconsole.net/">Spectre.Console</a>, so it&#8217;s pretty). Pick your provider, enter your API key, and you&#8217;re done. The CLI stores everything securely.</p>



<p class="wp-block-paragraph">Then generate:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;a robot painting a landscape&quot;</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it. The image appears in your current directory.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Where Do My Secrets Live?</h2>



<p class="wp-block-paragraph">Short version:&nbsp;<code>t2i config</code>&nbsp;stores credentials securely on your machine — DPAPI-encrypted on Windows,&nbsp;<code>0600</code>-permissioned file on macOS/Linux. Env vars are for CI only. CLI&nbsp;<code>--api-key</code>&nbsp;is for one-off tests.</p>



<p class="wp-block-paragraph">For the full resolution chain, CI/CD guidance, and the do/don&#8217;t table, see&nbsp;<strong><a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.Text2Image/docs/cli-secrets.md">docs/cli-secrets.md</a></strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2601.png" alt="☁" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Providers in Lite</h2>



<p class="wp-block-paragraph">This release ships with two cloud providers on Azure AI Foundry:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Provider</th><th class="has-text-align-left" data-align="left">Model</th><th class="has-text-align-left" data-align="left">Best For</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><code>foundry-flux2</code></td><td class="has-text-align-left" data-align="left">FLUX.2 Pro</td><td class="has-text-align-left" data-align="left">High-quality, fine control, batch jobs</td></tr><tr><td class="has-text-align-left" data-align="left"><code>foundry-mai2</code></td><td class="has-text-align-left" data-align="left">MAI-Image-2</td><td class="has-text-align-left" data-align="left">Synchronous API, rich prompts, fast iteration</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">More providers (Anthropic, OpenAI) coming in future releases.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Useful Commands</h2>



<p class="wp-block-paragraph">Here&#8217;s the CLI cheat sheet:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># List available providers</div><div class="cm-line">t2i providers</div><div class="cm-line"></div><div class="cm-line"># Run health checks (config + API connectivity)</div><div class="cm-line">t2i doctor</div><div class="cm-line"></div><div class="cm-line"># Show stored secrets (redacted)</div><div class="cm-line">t2i secrets list</div><div class="cm-line"></div><div class="cm-line"># Display version + commit SHA</div><div class="cm-line">t2i version</div><div class="cm-line"></div><div class="cm-line"># Interactive config setup</div><div class="cm-line">t2i config</div><div class="cm-line"></div><div class="cm-line"># Generate with default provider</div><div class="cm-line">t2i &quot;a cyberpunk cityscape at night&quot;</div><div class="cm-line"></div><div class="cm-line"># Generate with specific provider, dimensions, and output file</div><div class="cm-line">t2i &quot;my prompt&quot; \</div><div class="cm-line">  --provider foundry-mai2 \</div><div class="cm-line">  --width 1024 \</div><div class="cm-line">  --height 1024 \</div><div class="cm-line">  --output ./my-image.png</div><div class="cm-line"></div><div class="cm-line"># Show help</div><div class="cm-line">t2i --help</div><div class="cm-line">t2i generate --help</div><div class="cm-line"></div><div class="cm-line"># Teach your AI agent</div><div class="cm-line">t2i init</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Switching Models (v0.10.0+)</h2>



<p class="wp-block-paragraph">Both providers support multiple model variants. By default,&nbsp;<code>foundry-mai2</code>&nbsp;uses&nbsp;<code>MAI-Image-2</code>&nbsp;and&nbsp;<code>foundry-flux2</code>&nbsp;uses&nbsp;<code>FLUX.2-pro</code>. To switch models:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Use MAI-Image-2e</div><div class="cm-line">t2i config set foundry-mai2.model MAI-Image-2e</div><div class="cm-line"></div><div class="cm-line"># Use FLUX.2 Flex for text-heavy design and logos</div><div class="cm-line">t2i config set foundry-flux2.model FLUX.2-flex</div><div class="cm-line"></div><div class="cm-line"># View your configuration</div><div class="cm-line">t2i config show</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The&nbsp;<code>config show</code>&nbsp;command displays your endpoint and model in plain text, with only the API key masked. This makes it easy to verify your setup without revealing sensitive credentials.</p>



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Teach Your AI Agent —&nbsp;<code>t2i init</code></h2>



<p class="wp-block-paragraph">Inspired by&nbsp;<a href="https://aspire.dev/get-started/ai-coding-agents/">Aspire&#8217;s agent init pattern</a>, t2i now ships with a skill file your AI coding agent can read. Run this in any repo:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i init</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That writes a&nbsp;<code>SKILL.md</code>&nbsp;to both&nbsp;<code>.github/skills/t2i/</code>&nbsp;and&nbsp;<code>.claude/skills/t2i/</code>. From that point on, GitHub Copilot, Claude Code, and any MCP-aware agent know:</p>



<ul class="wp-block-list">
<li>Which <code>t2i</code> commands exist and when to use each one</li>



<li>How to set up secrets safely (env vars first, never commit keys)</li>



<li>The full provider list and which one to default to</li>



<li>Common workflows: first-time setup, single image, batch loops</li>
</ul>



<p class="wp-block-paragraph">Want only one target?</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i init --target github   # only .github/skills/t2i/</div><div class="cm-line">t2i init --target claude   # only .claude/skills/t2i/</div><div class="cm-line">t2i init --force           # overwrite existing skill files</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The canonical version of this skill also lives in this repo at&nbsp;<code>.github/skills/t2i/SKILL.md</code>&nbsp;— that means if you open the ElBruno.Text2Image source itself in Copilot or Claude Code, your agent already knows how to drive the CLI.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Coming Soon — Other Platforms</h2>



<h3 class="wp-block-heading">winget</h3>



<p class="wp-block-paragraph">The manifest stub is already in the repo (<code>winget/manifests/E/ElBruno/Text2Image/0.10.0/</code>). First submission to&nbsp;<a href="https://github.com/microsoft/winget-pkgs">microsoft/winget-pkgs</a>&nbsp;is queued. Automation will come in v0.2.0.</p>



<h3 class="wp-block-heading">Homebrew</h3>



<p class="wp-block-paragraph">Tap&nbsp;<code>elbruno/elbruno</code>&nbsp;is planned for v0.2.0.</p>



<h3 class="wp-block-heading">macOS Intel (osx-x64)</h3>



<p class="wp-block-paragraph">GitHub&#8217;s macOS-13 runner queue has been a bit slow lately. Intel Mac users should use&nbsp;<code>dotnet tool install</code>&nbsp;for now. The self-contained binary will return once the runners stabilize.</p>



<h3 class="wp-block-heading">Full Edition (Local GPU)</h3>



<p class="wp-block-paragraph">The&nbsp;<strong>Cli.Full</strong>&nbsp;edition — supporting local inference with ONNX Runtime (CPU, CUDA, DirectML, NPU) — is coming in a future release. ~200 MB, separate package.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sample Usages</h2>



<p class="wp-block-paragraph">Here are some fun one-liners to try:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">t2i &quot;a cyberpunk taco truck at sunset, cinematic lighting, volumetric fog&quot;</div><div class="cm-line"></div><div class="cm-line">t2i &quot;a low-poly 3D render of a friendly robot waving&quot; --provider foundry-mai2</div><div class="cm-line"></div><div class="cm-line">t2i &quot;minimalist line art of a cat reading a book&quot; --width 1024 --height 1024 --output cat.png</div><div class="cm-line"></div><div class="cm-line">t2i &quot;watercolor painting of the Madrid skyline at dusk, golden hour&quot; --output madrid.png</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><strong>Tip:</strong>&nbsp;The better your prompt, the better the image. Be specific about style (cinematic, watercolor, line art), mood (peaceful, energetic), and composition (wide-angle, close-up). Models reward detail.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f91d.png" alt="🤝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Let&#8217;s Go</h2>



<p class="wp-block-paragraph"><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli/0.1.0">Download it now</a>, run&nbsp;<code>t2i config</code>, and start generating. Found a bug?&nbsp;<a href="https://github.com/elbruno/ElBruno.Text2Image/issues">File an issue</a>.</p>



<p class="wp-block-paragraph"><strong>Links:</strong></p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>NuGet:</strong> <a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cli/0.10.0">ElBruno.Text2Image.Cli/0.10.0</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4c2.png" alt="📂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Release:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/releases/tag/cli-v0.10.0">github.com/elbruno/ElBruno.Text2Image/releases/tag/cli-v0.10.0</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d6.png" alt="📖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Full Docs:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/cli-tool.md">docs/cli-tool.md</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5bc.png" alt="🖼" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Hero image prompt:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/blogs/20260420-introducing-t2i-cli-image-prompt.md">docs/blogs/20260420-introducing-t2i-cli-image-prompt.md</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f41b.png" alt="🐛" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Issues:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/issues">github.com/elbruno/ElBruno.Text2Image/issues</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/20/%f0%9f%9a%80-meet-t2i-the-elbruno-text2image-cli/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40237</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png">
			<media:title type="html">t2i-50</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/t2i-50.png?w=640"/>
	</item>
		<item>
		<title>&#128506;️ AOT-Friendly DTO Mapping in .NET</title>
		<link>https://elbruno.com/2026/04/16/%f0%9f%97%ba%ef%b8%8f-aot-friendly-dto-mapping-in-net/</link>
					<comments>https://elbruno.com/2026/04/16/%f0%9f%97%ba%ef%b8%8f-aot-friendly-dto-mapping-in-net/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 16 Apr 2026 22:13:46 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[.Net]]></category>
		<category><![CDATA[AOT]]></category>
		<category><![CDATA[AOT Mapper]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[Data Mapping]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[NuGet Packages]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40228</guid>

					<description><![CDATA[Hi! So, I&#8217;ve been away from any scenario that involves and needs data mapping, until I get to OpenClawNet. So I got to the point that I need to map my entities to DTOs? I read this a lot &#62;&#62; runtime reflection doesn&#8217;t play nice with AOT and trimming. 😅 So I SQUAD myselft and built: 👉 ElBruno.AotMapper [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="540" src="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png?w=1024" alt="" class="wp-image-40230" srcset="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png 1424w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">So, I&#8217;ve been away from any scenario that involves and needs data mapping, until I get to <a href="https://www.linkedin.com/feed/update/urn:li:activity:7449425001541386241/">OpenClawNet</a>. So I got to the point that I need to map my entities to DTOs?</p>



<p class="wp-block-paragraph">I read this a lot &gt;&gt; <strong>runtime reflection doesn&#8217;t play nice with AOT and trimming.</strong> <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">So I SQUAD myselft and built:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><a href="https://www.nuget.org/packages/ElBruno.AotMapper" target="_blank" rel="noreferrer noopener">ElBruno.AotMapper</a></strong></p>



<p class="wp-block-paragraph">A Roslyn source-generator library that creates all your mapping code&nbsp;<strong>at compile time</strong>. No reflection. No dynamic IL. Just clean, generated C# that works everywhere — including NativeAOT and trimmed apps.</p>



<p class="wp-block-paragraph"><em><strong>Note: </strong>this is a WIP, so I expect to learn more during my usage. If you find any issue or have any ideas, please drop an issue on the repo, or even better &gt; a PR with some cool fixes / updates!</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph">Let me show you how it works. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Install</h2>



<p class="wp-block-paragraph">Two packages — the core attributes and the generator:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet add package ElBruno.AotMapper</div><div class="cm-line">dotnet add package ElBruno.AotMapper.Generator</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it. The generator runs during build and creates extension methods for you automatically.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Simplest Mapping — One Attribute, Done</h2>



<p class="wp-block-paragraph">Let&#8217;s start with the basics. You have a&nbsp;<code>Product</code>&nbsp;entity and you need a DTO:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">AotMapper</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">Product</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">int</span> <span class="tok-variableName">Id</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; }</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Name</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; } <span class="tok-operator">=</span> <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>;</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">decimal</span> <span class="tok-variableName">Price</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; }</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-variableName">ProductCategory</span> <span class="tok-variableName">Category</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; }</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-variableName">List</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">Tags</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; } <span class="tok-operator">=</span> <span class="tok-keyword">new</span>();</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">enum</span> <span class="tok-variableName">ProductCategory</span> { <span class="tok-variableName">Library</span>, <span class="tok-variableName">Tool</span>, <span class="tok-variableName">Framework</span> }</div><div class="cm-line"></div><div class="cm-line">[<span class="tok-variableName">MapFrom</span>(<span class="tok-keyword">typeof</span>(<span class="tok-variableName">Product</span>))]</div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">partial</span> <span class="tok-keyword">record</span> <span class="tok-variableName tok-definition">ProductDto</span>(<span class="tok-typeName">int</span> <span class="tok-variableName">Id</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">Name</span>, <span class="tok-typeName">decimal</span> <span class="tok-variableName">Price</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">Category</span>, <span class="tok-variableName">List</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">Tags</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">See that&nbsp;<code>[MapFrom]</code>&nbsp;attribute? That&#8217;s the magic. The generator sees it, matches properties by name, and creates a&nbsp;<code>.ToProductDto()</code>&nbsp;extension method for you.</p>



<p class="wp-block-paragraph">Now you use it like this:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">product</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">Product</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Id</span> <span class="tok-operator">=</span> <span class="tok-number">42</span>,</div><div class="cm-line">    <span class="tok-variableName">Name</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;ElBruno.AotMapper&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">Price</span> <span class="tok-operator">=</span> <span class="tok-number">0.0</span><span class="tok-variableName">m</span>,</div><div class="cm-line">    <span class="tok-variableName">Category</span> <span class="tok-operator">=</span> <span class="tok-variableName">ProductCategory</span>.<span class="tok-variableName">Library</span>,</div><div class="cm-line">    <span class="tok-variableName">Tags</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">List</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> { <span class="tok-string">&quot;aot&quot;</span>, <span class="tok-string">&quot;mapper&quot;</span>, <span class="tok-string">&quot;dotnet&quot;</span> }</div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">dto</span> <span class="tok-operator">=</span> <span class="tok-variableName">product</span>.<span class="tok-variableName">ToProductDto</span>();</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;Product: {dto.Name} (#{dto.Id})&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;  Category: {dto.Category}&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;  Tags: {string.Join(&quot;</span>, <span class="tok-string">&quot;, dto.Tags)}&quot;</span>);</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">I forgot how fun this was &gt; n<strong>o reflection, no runtime overhead,  AOT safe (I think, still checking stuff here) <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f500.png" alt="🔀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Remapping Properties with&nbsp;<code>[MapProperty]</code></h2>



<p class="wp-block-paragraph">What if your source and destination properties don&#8217;t have the same name? Easy — use&nbsp;<code>[MapProperty]</code>:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">AotMapper</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">Customer</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">int</span> <span class="tok-variableName">Id</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; }</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span> <span class="tok-variableName">FirstName</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; } <span class="tok-operator">=</span> <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>;</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span> <span class="tok-variableName">LastName</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; } <span class="tok-operator">=</span> <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>;</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Email</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; } <span class="tok-operator">=</span> <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>;</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-variableName">CustomerTier</span> <span class="tok-variableName">Tier</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">enum</span> <span class="tok-variableName">CustomerTier</span> { <span class="tok-variableName">Standard</span>, <span class="tok-variableName">Premium</span>, <span class="tok-variableName">Enterprise</span> }</div><div class="cm-line"></div><div class="cm-line">[<span class="tok-variableName">MapFrom</span>(<span class="tok-keyword">typeof</span>(<span class="tok-variableName">Customer</span>))]</div><div class="cm-line">[<span class="tok-variableName">MapProperty</span>(<span class="tok-variableName">nameof</span>(<span class="tok-variableName">Customer</span>.<span class="tok-variableName">FirstName</span>), <span class="tok-variableName">nameof</span>(<span class="tok-variableName">CustomerDto</span>.<span class="tok-variableName">Name</span>))]</div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">partial</span> <span class="tok-keyword">record</span> <span class="tok-variableName tok-definition">CustomerDto</span>(<span class="tok-typeName">int</span> <span class="tok-variableName">Id</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">Name</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">Email</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">Tier</span>);</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Here&nbsp;<code>FirstName</code>&nbsp;on the source gets mapped to&nbsp;<code>Name</code>&nbsp;on the DTO. The generator handles it at build time — zero surprises at runtime.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Minimal API — Full Example</h2>



<p class="wp-block-paragraph">Here&#8217;s where it gets fun. Let&#8217;s use AotMapper in an ASP.NET Core Minimal API.</p>



<p class="wp-block-paragraph">First, add the ASP.NET Core integration:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet add package ElBruno.AotMapper.AspNetCore</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then wire it up:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">MinimalApiSample</span>.<span class="tok-variableName">Models</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">AotMapper</span>.<span class="tok-variableName">AspNetCore</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">builder</span> <span class="tok-operator">=</span> <span class="tok-variableName">WebApplication</span>.<span class="tok-variableName">CreateBuilder</span>(<span class="tok-variableName">args</span>);</div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">Services</span>.<span class="tok-variableName">AddAotMapper</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">app</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">Build</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">customers</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">List</span><span class="tok-operator">&lt;</span><span class="tok-variableName">Customer</span><span class="tok-operator">&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">new</span>() { <span class="tok-variableName">Id</span> <span class="tok-operator">=</span> <span class="tok-number">1</span>, <span class="tok-variableName">FirstName</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Bruno&quot;</span>, <span class="tok-variableName">LastName</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Capuano&quot;</span>,</div><div class="cm-line">            <span class="tok-variableName">Email</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;bruno@example.com&quot;</span>, <span class="tok-variableName">Tier</span> <span class="tok-operator">=</span> <span class="tok-variableName">CustomerTier</span>.<span class="tok-variableName">Enterprise</span> },</div><div class="cm-line">    <span class="tok-keyword">new</span>() { <span class="tok-variableName">Id</span> <span class="tok-operator">=</span> <span class="tok-number">2</span>, <span class="tok-variableName">FirstName</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Jane&quot;</span>, <span class="tok-variableName">LastName</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Smith&quot;</span>,</div><div class="cm-line">            <span class="tok-variableName">Email</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;jane@example.com&quot;</span>, <span class="tok-variableName">Tier</span> <span class="tok-operator">=</span> <span class="tok-variableName">CustomerTier</span>.<span class="tok-variableName">Premium</span> }</div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapGet</span>(<span class="tok-string">&quot;/customers&quot;</span>, () <span class="tok-operator">=&gt;</span> <span class="tok-variableName">customers</span>.<span class="tok-variableName">Select</span>(<span class="tok-variableName">c</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">c</span>.<span class="tok-variableName">ToCustomerDto</span>()));</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapGet</span>(<span class="tok-string">&quot;/customers/{id}&quot;</span>, (<span class="tok-typeName">int</span> <span class="tok-variableName">id</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">customer</span> <span class="tok-operator">=</span> <span class="tok-variableName">customers</span>.<span class="tok-variableName">FirstOrDefault</span>(<span class="tok-variableName">c</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">c</span>.<span class="tok-variableName">Id</span> <span class="tok-operator">==</span> <span class="tok-variableName">id</span>);</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">customer</span> <span class="tok-keyword">is</span> <span class="tok-atom">null</span> <span class="tok-operator">?</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">NotFound</span>() : <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-variableName">customer</span>.<span class="tok-variableName">ToCustomerDto</span>());</div><div class="cm-line">});</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">Run</span>();</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">All the magic in the endpoing handlers with &gt; <code>c.ToCustomerDto()</code> (that&#8217;s the generated extension method)</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5c4.png" alt="🗄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> EF Core — In-Memory Mapping and SQL Projections</h2>



<p class="wp-block-paragraph">If you&#8217;re using Entity Framework, there&#8217;s a package for that, and I have some samples in the repo (I think I can do more here &#8230; I didn&#8217;t have time yet for this) </p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e9.png" alt="🧩" class="wp-smiley" style="height: 1em; max-height: 1em;" /> More Attributes You Should Know</h2>



<p class="wp-block-paragraph">The library has a few more tricks:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Attribute</th><th class="has-text-align-left" data-align="left">What it does</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><code>[MapFrom(typeof(T))]</code></td><td class="has-text-align-left" data-align="left">Map&nbsp;<strong>from</strong>&nbsp;a source type to this DTO</td></tr><tr><td class="has-text-align-left" data-align="left"><code>[MapTo(typeof(T))]</code></td><td class="has-text-align-left" data-align="left">Map&nbsp;<strong>to</strong>&nbsp;a destination type from this source</td></tr><tr><td class="has-text-align-left" data-align="left"><code>[MapProperty("src", "dest")]</code></td><td class="has-text-align-left" data-align="left">Remap a property with a different name</td></tr><tr><td class="has-text-align-left" data-align="left"><code>[MapIgnore]</code></td><td class="has-text-align-left" data-align="left">Skip a property during mapping</td></tr><tr><td class="has-text-align-left" data-align="left"><code>[MapConverter(typeof(T))]</code></td><td class="has-text-align-left" data-align="left">Use a custom converter for a property</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">And for custom converters, you implement&nbsp;<code>IMapConverter&lt;TSource, TDestination&gt;</code>:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">UpperCaseConverter</span> : <span class="tok-variableName">IMapConverter</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span>, <span class="tok-typeName">string</span><span class="tok-operator">&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Convert</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">source</span>) <span class="tok-operator">=&gt;</span> <span class="tok-variableName">source</span>.<span class="tok-variableName">ToUpperInvariant</span>();</div><div class="cm-line">}</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> How It Works Under the Hood</h2>



<p class="wp-block-paragraph">The flow is simple:</p>



<ol class="wp-block-list">
<li>You add <code>[MapFrom]</code> (or <code>[MapTo]</code>) to your DTO</li>



<li>During build, the Roslyn incremental source generator kicks in</li>



<li>It matches source and destination properties by name and type</li>



<li>It generates strongly-typed extension methods (like <code>.ToProductDto()</code>)</li>



<li>If something doesn&#8217;t match, you get a <strong>compile-time error</strong> — not a runtime surprise</li>
</ol>



<p class="wp-block-paragraph">That&#8217;s the whole point:&nbsp;<strong>move the errors to build time, move the mapping to generated code, and keep the runtime clean.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4c2.png" alt="📂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Samples</h2>



<p class="wp-block-paragraph">The repo includes three complete samples you can run right now:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><a href="https://github.com/elbruno/ElBruno.AotMapper/tree/main/src/samples/NativeAotSample">NativeAotSample</a></strong> — Basic mapping in a NativeAOT console app</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><a href="https://github.com/elbruno/ElBruno.AotMapper/tree/main/src/samples/MinimalApiSample">MinimalApiSample</a></strong> — ASP.NET Core Minimal API with DI</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5c4.png" alt="🗄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><a href="https://github.com/elbruno/ElBruno.AotMapper/tree/main/src/samples/EfProjectionSample">EfProjectionSample</a></strong> — EF Core with in-memory and SQL projections</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Resources</h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>NuGet</strong>: <a href="https://www.nuget.org/packages/ElBruno.AotMapper">ElBruno.AotMapper</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f419.png" alt="🐙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>GitHub</strong>: <a href="https://github.com/elbruno/ElBruno.AotMapper">github.com/elbruno/ElBruno.AotMapper</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d6.png" alt="📖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Docs</strong>: <a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.AotMapper/docs/quick-start.md">Quick Start</a> | <a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.AotMapper/docs/supported-mappings.md">Supported Mappings</a> | <a href="https://file+.vscode-resource.vscode-cdn.net/c%3A/src/ElBruno.AotMapper/docs/ef-integration.md">EF Integration</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4dc.png" alt="📜" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>License</strong>: MIT</li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/16/%f0%9f%97%ba%ef%b8%8f-aot-friendly-dto-mapping-in-net/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40228</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png">
			<media:title type="html">Gemini_Generated_Image_7a9gt7a9gt7a9gt7</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_7a9gt7a9gt7a9gt7.png?w=1024"/>
	</item>
		<item>
		<title>&#128444;️ MAI-Image-2 Just Dropped — And .NET Support Is Already Here</title>
		<link>https://elbruno.com/2026/04/13/%f0%9f%96%bc%ef%b8%8f-mai-image-2-just-dropped-and-net-support-is-already-here/</link>
					<comments>https://elbruno.com/2026/04/13/%f0%9f%96%bc%ef%b8%8f-mai-image-2-just-dropped-and-net-support-is-already-here/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 13 Apr 2026 14:39:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Image Generation]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[NuGet Packages]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=40219</guid>

					<description><![CDATA[Hi! When Microsoft announced&#160;MAI-Image-2, I immediately thought:&#160;&#8220;I need to add this to ElBruno.Text2Image. Today.&#8221; So I did. 😄 MAI-Image-2 is Microsoft&#8217;s new image generation model on Microsoft Foundry — high-quality generation, a synchronous API (no polling!), a 32K character prompt limit, and flexible dimensions. And it&#8217;s already supported in ElBruno.Text2Image with the same clean interface you already know. Let me [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">When Microsoft announced&nbsp;<a href="https://microsoft.ai/news/introducing-MAI-Image-2/">MAI-Image-2</a>, I immediately thought:&nbsp;<em>&#8220;I need to add this to ElBruno.Text2Image. Today.&#8221;</em></p>



<p class="wp-block-paragraph">So I did. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph"><strong>MAI-Image-2</strong> is Microsoft&#8217;s new image generation model on Microsoft Foundry — high-quality generation, a <strong>synchronous API</strong> (no polling!), a 32K character prompt limit, and flexible dimensions. And it&#8217;s already supported in <strong>ElBruno.Text2Image</strong> with the same clean interface you already know.</p>



<p class="wp-block-paragraph">Let me show you how it works.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2601.png" alt="☁" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Getting Started — MAI-Image-2 on Azure AI Foundry</h2>



<p class="wp-block-paragraph">MAI-Image-2 delivers high-quality image generation with a simpler developer experience than FLUX.2. The API is synchronous — you send a request, you get an image back. No 202 status codes, no polling loops, no waiting callbacks. Just a prompt and a picture.</p>



<p class="wp-block-paragraph">Here&#8217;s all you need:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">Text2Image</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">Text2Image</span>.<span class="tok-variableName">Foundry</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">generator</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">MaiImage2Generator</span>(</div><div class="cm-line">    <span class="tok-variableName">endpoint</span>: <span class="tok-string">&quot;https://your-resource.services.ai.azure.com&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">apiKey</span>: <span class="tok-string">&quot;your-api-key&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">modelId</span>: <span class="tok-string">&quot;MAI-Image-2&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">result</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">generator</span>.<span class="tok-variableName">GenerateAsync</span>(</div><div class="cm-line">    <span class="tok-string">&quot;a futuristic cityscape with neon lights, cyberpunk style&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-variableName">result</span>.<span class="tok-variableName">SaveAsync</span>(<span class="tok-string">&quot;mai-image2-output.png&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;Generated in {result.InferenceTimeMs}ms&quot;</span>);</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Setting up credentials</h3>



<p class="wp-block-paragraph">The library reads from User Secrets, environment variables, or&nbsp;<code>appsettings.json</code>. For local development:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-powershell"><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">user-secrets</span> <span class="tok-variableName">set</span> <span class="tok-variableName">MAI_IMAGE2_ENDPOINT</span> <span class="tok-string">&quot;https://your-resource.services.ai.azure.com&quot;</span></div><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">user-secrets</span> <span class="tok-variableName">set</span> <span class="tok-variableName">MAI_IMAGE2_API_KEY</span> <span class="tok-string">&quot;your-api-key-here&quot;</span></div><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">user-secrets</span> <span class="tok-variableName">set</span> <span class="tok-variableName">MAI_IMAGE2_MODEL_ID</span> <span class="tok-string">&quot;MAI-Image-2&quot;</span></div></code></pre>
		</div>
	</div>
</div>


<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Fun fact:</strong>&nbsp;MAI-Image-2 uses a dedicated&nbsp;<code>/mai/v1/images/generations</code>&nbsp;endpoint. The library handles this automatically — just provide your&nbsp;<code>.services.ai.azure.com</code>&nbsp;base URL and it builds the correct API path for you.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Key Differences from FLUX.2</h2>



<p class="wp-block-paragraph">If you&#8217;re already using FLUX.2 with this library, here&#8217;s how MAI-Image-2 compares:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Feature</th><th class="has-text-align-left" data-align="left">MAI-Image-2</th><th class="has-text-align-left" data-align="left">FLUX.2</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><strong>API style</strong></td><td class="has-text-align-left" data-align="left">Synchronous (direct response)</td><td class="has-text-align-left" data-align="left">Asynchronous (202 + polling)</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>API path</strong></td><td class="has-text-align-left" data-align="left"><code>/mai/v1/images/generations</code></td><td class="has-text-align-left" data-align="left">BFL provider path</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Prompt limit</strong></td><td class="has-text-align-left" data-align="left">32,000 characters</td><td class="has-text-align-left" data-align="left">~1,000 characters</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Min dimensions</strong></td><td class="has-text-align-left" data-align="left">768px (per side)</td><td class="has-text-align-left" data-align="left">256px</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Max dimensions</strong></td><td class="has-text-align-left" data-align="left">1M total pixels</td><td class="has-text-align-left" data-align="left">Model-dependent</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Interface</strong></td><td class="has-text-align-left" data-align="left"><code>IImageGenerator</code></td><td class="has-text-align-left" data-align="left"><code>IImageGenerator</code></td></tr><tr><td class="has-text-align-left" data-align="left"><strong>DI support</strong></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Same pattern</td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Same pattern</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Endpoint auto-conversion</strong></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td class="has-text-align-left" data-align="left"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">The synchronous API is a big deal for developer experience. No more writing polling loops or handling intermediate states. Send a prompt, get an image. Done.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50c.png" alt="🔌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Same Interface, Multiple Backends</h2>



<p class="wp-block-paragraph"><strong><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e9.png" alt="🧩" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Microsoft.Extensions.AI Compatible</strong></p>



<p class="wp-block-paragraph">Every generator in the library — including the new MaiImage2Generator — implements the standard Microsoft.Extensions.AI.IImageGenerator interface from the<br />Microsoft.Extensions.AI.Abstractions package. This means you can use ElBruno.Text2Image as a drop-in provider anywhere the MEAI abstraction is expected — dependency<br />injection, middleware pipelines, or any framework that programs against IImageGenerator. Cloud or local, FLUX.2 or MAI-Image-2 or Stable Diffusion — they all plug<br />into the same standard .NET AI contract.</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// MAI-Image-2 (cloud)</span></div><div class="cm-line"><span class="tok-variableName">IImageGenerator</span> <span class="tok-variableName">generator</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">MaiImage2Generator</span>(<span class="tok-variableName">endpoint</span>, <span class="tok-variableName">apiKey</span>, <span class="tok-variableName">modelId</span>: <span class="tok-string">&quot;MAI-Image-2&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// FLUX.2 Pro (cloud)</span></div><div class="cm-line"><span class="tok-variableName">IImageGenerator</span> <span class="tok-variableName">generator</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">Flux2Generator</span>(<span class="tok-variableName">endpoint</span>, <span class="tok-variableName">apiKey</span>, <span class="tok-variableName">modelId</span>: <span class="tok-string">&quot;FLUX.2-pro&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Stable Diffusion 1.5 (local)</span></div><div class="cm-line"><span class="tok-variableName">IImageGenerator</span> <span class="tok-variableName">generator</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">StableDiffusion15</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Same API for all three</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">result</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">generator</span>.<span class="tok-variableName">GenerateAsync</span>(<span class="tok-string">&quot;a beautiful landscape&quot;</span>);</div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f489.png" alt="💉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Dependency Injection</h2>



<p class="wp-block-paragraph">If you&#8217;re building with DI, the library has an extension method ready to go:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-variableName">services</span>.<span class="tok-variableName">AddMaiImage2Generator</span>(</div><div class="cm-line">    <span class="tok-variableName">endpoint</span>: <span class="tok-string">&quot;https://your-resource.services.ai.azure.com&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">apiKey</span>: <span class="tok-string">&quot;your-api-key&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">modelId</span>: <span class="tok-string">&quot;MAI-Image-2&quot;</span>);</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Same pattern as the FLUX.2 registration. Inject&nbsp;<code>IImageGenerator</code>&nbsp;and you&#8217;re done.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Links</h2>



<ul class="wp-block-list">
<li><strong>Repository:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image">github.com/elbruno/ElBruno.Text2Image</a></li>



<li><strong>NuGet:</strong> <a href="https://www.nuget.org/packages/ElBruno.Text2Image.Foundry">nuget.org/packages/ElBruno.Text2Image.Foundry</a></li>



<li><strong>MAI-Image-2 Announcement:</strong> <a href="https://microsoft.ai/news/introducing-MAI-Image-2/">Introducing MAI-Image-2</a></li>



<li><strong>Setup Guide:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/mai-image-2-setup-guide.md">MAI-Image-2 Setup Guide</a></li>



<li><strong>v0.8.0 Release:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/releases/tag/v0.8.0">github.com/elbruno/ElBruno.Text2Image/releases/tag/v0.8.0</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/13/%f0%9f%96%bc%ef%b8%8f-mai-image-2-just-dropped-and-net-support-is-already-here/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">40219</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/image.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/image.png">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#128640; Bing Open-Sources New Embedding Models — Now Available in C# with LocalEmbeddings.Harrier</title>
		<link>https://elbruno.com/2026/04/08/%f0%9f%9a%80-bing-open-sources-new-embedding-models-now-available-in-c-with-localembeddings-harrier/</link>
					<comments>https://elbruno.com/2026/04/08/%f0%9f%9a%80-bing-open-sources-new-embedding-models-now-available-in-c-with-localembeddings-harrier/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 08 Apr 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[Embeddings]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39431</guid>

					<description><![CDATA[The Microsoft Bing Search team just dropped something big: 👉 https://blogs.bing.com/search/April-2026/Microsoft-Open-Sources-Industry-Leading-Embedding-Model They’ve open-sourced a new set of high-quality embedding models, designed to be competitive with state-of-the-art approaches — and ready to run in modern AI workflows. And here’s the fun part 👇 You can already use these models from C#/.NET. 🤖 From Announcement to .NET [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">The Microsoft Bing Search team just dropped something big:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://blogs.bing.com/search/April-2026/Microsoft-Open-Sources-Industry-Leading-Embedding-Model" target="_blank" rel="noreferrer noopener">https://blogs.bing.com/search/April-2026/Microsoft-Open-Sources-Industry-Leading-Embedding-Model</a></p>



<p class="wp-block-paragraph">They’ve <strong>open-sourced a new set of high-quality embedding models</strong>, designed to be competitive with state-of-the-art approaches — and ready to run in modern AI workflows.</p>



<p class="wp-block-paragraph">And here’s the fun part <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph"><strong>You can already use these models from C#/.NET.</strong></p>



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> From Announcement to .NET in Minutes</h2>



<p class="wp-block-paragraph">I integrated these new models into a C# library:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Harrier">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Harrier</a></p>



<p class="wp-block-paragraph">Now you can:</p>



<ul class="wp-block-list">
<li>Run embeddings locally</li>



<li>Use Bing’s open models</li>



<li>Plug into your .NET apps</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f30d.png" alt="🌍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> A Personal Take: Multilingual FTW</h2>



<p class="wp-block-paragraph">Harrier shines with <strong>multilingual support</strong>.</p>



<p class="wp-block-paragraph">You can embed content like:</p>



<ul class="wp-block-list">
<li>&#8220;Hello world!&#8221;</li>



<li>&#8220;Hola mundo!&#8221;</li>
</ul>



<p class="wp-block-paragraph">…and still get meaningful semantic relationships — out of the box.</p>



<p class="wp-block-paragraph">Perfect for global-ready AI apps <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f30e.png" alt="🌎" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e9.png" alt="🧩" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET + AI Ecosystem</h2>



<p class="wp-block-paragraph">You can combine this with:</p>



<ul class="wp-block-list">
<li>Foundry Local or Ollama for local AI</li>



<li>Microsoft Foundry for cloud</li>



<li>Microsoft Agent Framework for agents</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Getting Started</h2>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Harrier">https://www.nuget.org/packages/ElBruno.LocalEmbeddings.Harrier</a></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.LocalEmbeddings.Harrier;

await using var generator = await HarrierEmbeddingGenerator.CreateAsync();

var embeddings = await generator.GenerateAsync(&#91;"Hello world!", "Hola mundo!"]);
Console.WriteLine($"Dimensions: {embeddings&#91;0].Vector.Length}"); // 640
</pre></div>


<p class="wp-block-paragraph">Samples:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/HarrierConsoleApp">https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/HarrierConsoleApp</a><br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/HarrierMultilingualSample">https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/HarrierMultilingualSample</a></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/08/%f0%9f%9a%80-bing-open-sources-new-embedding-models-now-available-in-c-with-localembeddings-harrier/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39431</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/small.png">
			<media:title type="html">small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>I Built a .NET 10 Knowledge Graph Builder (Inspired by Karpathy)</title>
		<link>https://elbruno.com/2026/04/06/i-built-a-net-10-knowledge-graph-builder-inspired-by-karpathy/</link>
					<comments>https://elbruno.com/2026/04/06/i-built-a-net-10-knowledge-graph-builder-inspired-by-karpathy/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Tue, 07 Apr 2026 00:05:06 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GenAI]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[LLMs]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39425</guid>

					<description><![CDATA[Hi! Earlier this year,&#160;Andrej Karpathy tweeted about using LLMs as &#8220;knowledge compilers&#8221;&#160;— a mind-bending idea: instead of asking an LLM questions, feed it raw data (papers, code, images) and let it automatically build a structured, navigable knowledge base. No RAG. No vector databases. Just pure understanding compiled into a graph. Then I saw&#160;@socialwithaayan showcase graphify&#160;— [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="704" height="384" src="https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png?w=704" alt="" class="wp-image-39428" srcset="https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png 704w, https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png?w=300 300w" sizes="(max-width: 704px) 100vw, 704px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Earlier this year,&nbsp;<a href="https://x.com/karpathy/status/2039805659525644595">Andrej Karpathy tweeted about using LLMs as &#8220;knowledge compilers&#8221;</a>&nbsp;— a mind-bending idea: instead of asking an LLM questions, feed it raw data (papers, code, images) and let it automatically build a structured, navigable knowledge base. No RAG. No vector databases. Just pure understanding compiled into a graph.</p>



<p class="wp-block-paragraph">Then I saw&nbsp;<a href="https://x.com/socialwithaayan/status/2041192946369007924">@socialwithaayan showcase graphify</a>&nbsp;— a Python tool that does exactly this for codebases. It reads your source code, extracts relationships, builds a knowledge graph, and exports it as an interactive visualization, Obsidian vault, Neo4j script, or JSON.</p>



<p class="wp-block-paragraph">I thought:&nbsp;<em>&#8220;This needs to exist in .NET.&#8221;</em></p>



<p class="wp-block-paragraph">So I built&nbsp;<a href="https://github.com/elbruno/graphify-dotnet"><strong>graphify-dotnet</strong></a>.</p>



<h2 class="wp-block-heading">What is graphify-dotnet?<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#what-is-graphify-dotnet"></a></h2>



<p class="wp-block-paragraph">graphify-dotnet is a .NET 10 tool that reads your codebase — all those files scattered across folders, all that implicit structure hidden in class hierarchies and imports — and transforms it into a&nbsp;<strong>visual knowledge graph</strong>. No more navigating by keyword search. Instead, you see the actual structure: which classes talk to each other, which modules form natural clusters, which functions are the &#8220;god nodes&#8221; that everything depends on.</p>



<p class="wp-block-paragraph">It&#8217;s a multi-stage pipeline:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: coldfusion; title: ; notranslate">
┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  Detect  │ -&gt; │ Extract  │ -&gt; │  Build   │ -&gt; │ Cluster  │
│  Files   │    │ Features │    │  Graph   │    │ (Louvain)│
└──────────┘    └──────────┘    └──────────┘    └──────────┘
                                                     │
                                                     v
┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  Export  │ &lt;- │  Report  │ &lt;- │ Analyze  │ &lt;- │ Clustered│
│ Formats  │    │Generator │    │  Graph   │    │  Graph   │
└──────────┘    └──────────┘    └──────────┘    └──────────┘
</pre></div>


<p class="wp-block-paragraph"><strong>Scan → Extract relationships → Build the graph → Find communities → Analyze structure → Export to multiple formats.</strong></p>



<h2 class="wp-block-heading">Let&#8217;s See It in Action<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#lets-see-it-in-action"></a></h2>



<p class="wp-block-paragraph">Here are some real commands you can run right now:</p>



<p class="wp-block-paragraph"><strong>Build a knowledge graph from your project:</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet run --project src/Graphify.Cli -- run .
</pre></div>


<p class="wp-block-paragraph"><strong>Query the graph for connections:</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet run --project src/Graphify.Cli -- query "what connects AuthService to Database?"
</pre></div>


<p class="wp-block-paragraph"><strong>Explain a node:</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet run --project src/Graphify.Cli -- explain "UserController"
</pre></div>


<p class="wp-block-paragraph"><strong>Export to multiple formats:</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet run --project src/Graphify.Cli -- export --format html
dotnet run --project src/Graphify.Cli -- export --format neo4j
</pre></div>


<p class="wp-block-paragraph">The HTML export gives you an interactive vis.js graph. Click nodes, search by concept, filter by community. The Neo4j export lets you load the entire graph into a real graph database.</p>



<h2 class="wp-block-heading">Key Features<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#key-features"></a></h2>



<ul class="wp-block-list">
<li><strong>Multi-language parsing</strong>: Python, TypeScript, JavaScript, Go, Rust, Java, C#, C++, and more via tree-sitter AST extraction</li>



<li><strong>Hybrid extraction</strong>: Deterministic AST parsing + AI semantic analysis for docs and images</li>



<li><strong>Graph clustering</strong>: Louvain community detection so you can see natural groupings in your architecture</li>



<li><strong>Confidence tracking</strong>: Every relationship tagged as EXTRACTED, INFERRED, or AMBIGUOUS</li>



<li><strong>Multiple export formats</strong>: JSON, HTML, SVG, GraphML, Wiki, Obsidian vault, Neo4j Cypher</li>



<li><strong>SHA256 caching</strong>: Skip unchanged files — incremental updates instead of full rebuilds</li>



<li><strong>MCP server</strong>: Integrate with Claude, Copilot, and other AI assistants</li>



<li><strong>Multimodal</strong>: Handles code, Markdown, PDFs, and images (diagrams, screenshots, whiteboards)</li>
</ul>



<h2 class="wp-block-heading">Why This Matters<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#why-this-matters"></a></h2>



<p class="wp-block-paragraph">Most developers navigate codebases by searching: &#8220;Find all usages of X.&#8221; &#8220;Show me the inheritance tree.&#8221; We&#8217;re stuck with keyword queries because we don&#8217;t have a semantic map.</p>



<p class="wp-block-paragraph">A knowledge graph changes that. Suddenly you see the&nbsp;<em>shape</em>&nbsp;of your system. You spot the core dependencies, the bottlenecks, the modules that should talk but don&#8217;t. You understand the architecture at a glance.</p>



<p class="wp-block-paragraph">And because it&#8217;s built from LLM-powered extraction, the graph understands&nbsp;<em>meaning</em>, not just syntax. It sees that your&nbsp;<code>Repository</code>&nbsp;class is about data access. It connects concepts across files. It finds hidden relationships.</p>



<figure class="wp-block-image size-large"><img loading="lazy" width="704" height="384" src="https://elbruno.com/wp-content/uploads/2026/04/bp02_small.png?w=704" alt="" class="wp-image-39429" srcset="https://elbruno.com/wp-content/uploads/2026/04/bp02_small.png 704w, https://elbruno.com/wp-content/uploads/2026/04/bp02_small.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/bp02_small.png?w=300 300w" sizes="(max-width: 704px) 100vw, 704px" /></figure>



<h2 class="wp-block-heading">Getting Started<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#getting-started"></a></h2>



<p class="wp-block-paragraph"><strong>Requirements</strong>: .NET 10 SDK</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
git clone https://github.com/elbruno/graphify-dotnet.git
cd graphify-dotnet

dotnet build graphify-dotnet.slnx

dotnet run --project src/Graphify.Cli -- run .
</pre></div>


<p class="wp-block-paragraph">That&#8217;s it. Run it on your own codebase and watch as your architecture unfolds.</p>



<h2 class="wp-block-heading">What&#8217;s Coming Next?<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#whats-coming-next"></a></h2>



<p class="wp-block-paragraph">The roadmap is ambitious. Based on Morpheus&#8217;s research, here&#8217;s what&#8217;s in the pipeline:</p>



<p class="wp-block-paragraph"><strong>Soon:</strong></p>



<ul class="wp-block-list">
<li><strong>Global dotnet tool</strong>: Install once with <code>dotnet tool install -g graphify</code>, then just run <code>graphify run .</code> anywhere</li>



<li><strong>Azure OpenAI support</strong>: Teams with Azure OpenAI deployments can now use graphify with those modelos</li>



<li><strong>Ollama / local models</strong>: Run entirely offline locally for privacy-sensitive code (healthcare, finance, defense)</li>



<li><strong>Watch mode</strong>: File watcher that increments the graph as you code, no full rebuilds needed</li>



<li><strong>Roslyn-powered C# extraction</strong>: Leverage .NET&#8217;s full compilation model for type-safe AST analysis (something the Python version can&#8217;t do)</li>
</ul>



<p class="wp-block-paragraph">I&#8217;m also exploring VS Code integration, Obsidian bidirectional sync, and cross-repository knowledge graphs.</p>



<h2 class="wp-block-heading">Star the Repo, Try It Out<a href="https://github.com/elbruno/graphify-dotnet/blob/main/docs/blog-post.md#star-the-repo-try-it-out"></a></h2>



<p class="wp-block-paragraph">This is open source, built for the .NET community.&nbsp;<a href="https://github.com/elbruno/graphify-dotnet">Head to GitHub and check it out.</a></p>



<p class="wp-block-paragraph">Clone it. Run it on your own projects. Build something with it. The pipeline is extensible — add custom extractors, export formats, or clustering algorithms.</p>



<p class="wp-block-paragraph">And if you find it useful, star the repo. Help us grow the graphify ecosystem in .NET.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/06/i-built-a-net-10-knowledge-graph-builder-inspired-by-karpathy/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39425</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png">
			<media:title type="html">bp01_small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/bp01_small.png?w=704"/>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/bp02_small.png?w=704"/>
	</item>
		<item>
		<title>&#128221; Convert Anything to Markdown in .NET — Meet ElBruno.MarkItDotNet</title>
		<link>https://elbruno.com/2026/04/04/%f0%9f%93%9d-convert-anything-to-markdown-in-net-meet-elbruno-markitdotnet/</link>
					<comments>https://elbruno.com/2026/04/04/%f0%9f%93%9d-convert-anything-to-markdown-in-net-meet-elbruno-markitdotnet/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Sat, 04 Apr 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[MarkDown]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[Open Source]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39420</guid>

					<description><![CDATA[Hi! You know that feeling when you&#8217;re building an&#160;AI pipeline&#160;or a&#160;RAG workflow&#160;and you realize: &#8220;Wait… I need to turn all these PDFs, Word docs, HTML pages, and random files into something my LLM can actually eat&#8221;? 😅 Yeah, me too. That&#8217;s exactly why I built: 👉 ElBruno.MarkItDotNet A .NET library that converts files to clean Markdown. Think of [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="682" src="https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=1024" alt="" class="wp-image-39422" srcset="https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png 1536w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">You know that feeling when you&#8217;re building an&nbsp;<strong>AI pipeline</strong>&nbsp;or a&nbsp;<strong>RAG workflow</strong>&nbsp;and you realize: &#8220;Wait… I need to turn all these PDFs, Word docs, HTML pages, and random files into something my LLM can actually eat&#8221;? <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">Yeah, me too. That&#8217;s exactly why I built:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet" target="_blank" rel="noreferrer noopener">ElBruno.MarkItDotNet</a></strong></p>



<p class="wp-block-paragraph">A <strong>.NET library</strong> that converts files to clean Markdown. Think of it as the <strong>.NET version of Python&#8217;s markitdown</strong> — this one with dependency injection, streaming support, and a plugin architecture. Because we&#8217;re C# developers and we like our things in this way. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f60e.png" alt="😎" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Getting Started<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-getting-started"></a></h2>



<p class="wp-block-paragraph">Install the NuGet package:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet add package ElBruno.MarkItDotNet
</pre></div>


<p class="wp-block-paragraph">And then… this is all you need:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.MarkItDotNet;

var converter = new MarkdownConverter();
var markdown = converter.ConvertToMarkdown("document.pdf");
Console.WriteLine(markdown);
</pre></div>


<p class="wp-block-paragraph">That&#8217;s it. PDF → Markdown. Done. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4c2.png" alt="📂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> What Can It Convert?<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-what-can-it-convert"></a></h2>



<p class="wp-block-paragraph">Here&#8217;s where it gets fun. The core package supports&nbsp;<strong>12 file formats</strong>&nbsp;out of the box:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Plain text</strong> (<code>.txt</code>, <code>.log</code>, <code>.md</code>)</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>JSON</strong> — pretty-printed and fenced</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>HTML / HTM</strong> — strips tags, keeps content</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>URLs</strong> — fetches and converts web pages</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Word DOCX</strong> — headings, tables, links, images, footnotes</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d5.png" alt="📕" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>PDF</strong> — word-level extraction with heading detection</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>CSV / TSV</strong> — clean Markdown tables</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>XML</strong> — structured fenced blocks</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2699.png" alt="⚙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>YAML / YML</strong> — fenced code blocks</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4f0.png" alt="📰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>RTF</strong> — rich text to Markdown</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>EPUB</strong> — ebooks to Markdown</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5bc.png" alt="🖼" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Images</strong> — <code>.jpg</code>, <code>.png</code>, <code>.gif</code>, <code>.bmp</code>, <code>.webp</code>, <code>.svg</code></li>
</ul>



<p class="wp-block-paragraph">And with the&nbsp;<strong>satellite packages</strong>, you get even more:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Package</th><th>What it does</th></tr></thead><tbody><tr><td><code>ElBruno.MarkItDotNet.Excel</code></td><td><code>.xlsx</code>&nbsp;spreadsheets → Markdown tables</td></tr><tr><td><code>ElBruno.MarkItDotNet.PowerPoint</code></td><td><code>.pptx</code>&nbsp;slides → Markdown with notes</td></tr><tr><td><code>ElBruno.MarkItDotNet.AI</code></td><td>AI-powered OCR, image captioning, audio transcription</td></tr><tr><td><code>ElBruno.MarkItDotNet.Whisper</code></td><td>Local audio transcription with Whisper (no API key!)</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Stream It — Because Large Files Are Real<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-stream-it--because-large-files-are-real"></a></h2>



<p class="wp-block-paragraph">One of the things I find that someone were requested was &gt;&gt; <strong>streaming API</strong>. When you&#8217;re processing a 500-page PDF, you don&#8217;t want to wait for the entire thing to load in memory. So:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using var stream = File.OpenRead("huge-document.pdf");

await foreach (var chunk in converter.ConvertStreamingAsync(stream, ".pdf"))
{
    Console.Write(chunk); // chunks arrive as they're processed
}
</pre></div>


<p class="wp-block-paragraph">This uses <code>IAsyncEnumerable&lt;string&gt;</code> — so it plays nicely with your async pipelines, web APIs, and real-time UIs. </p>



<p class="wp-block-paragraph">To be honest, I never faced this scenario before, but it really makes sense.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f489.png" alt="💉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Dependency Injection? Of Course<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-dependency-injection-of-course"></a></h2>



<p class="wp-block-paragraph">If you&#8217;re building a real app (not just a console demo), you&#8217;ll want the DI registration:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Program.cs or Startup
services.AddMarkItDotNet();          // core converters
services.AddMarkItDotNetExcel();     // Excel support
services.AddMarkItDotNetPowerPoint(); // PowerPoint support
services.AddMarkItDotNetWhisper();   // local audio transcription
</pre></div>


<p class="wp-block-paragraph">Then inject <code>IMarkdownService</code> wherever you need it:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class MyDocProcessor
{
    private readonly IMarkdownService _markdownService;

    public MyDocProcessor(IMarkdownService markdownService)
    {
        _markdownService = markdownService;
    }

    public async Task&lt;string&gt; ProcessAsync(Stream fileStream, string extension)
    {
        var result = await _markdownService.ConvertAsync(fileStream, extension);
        return result.Markdown;
    }
}
</pre></div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> AI-Powered Conversions<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-ai-powered-conversions"></a></h2>



<p class="wp-block-paragraph">This is where things get really interesting. And thanks Copilot CLI for suggesting this <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">The <strong>ElBruno.MarkItDotNet.AI</strong> package uses <code>Microsoft.Extensions.AI</code> and an <code>IChatClient</code> to power:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5bc.png" alt="🖼" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Image OCR &amp; captioning</strong> — describe what&#8217;s in an image</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d5.png" alt="📕" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Scanned PDF enhancement</strong> — detects low-text pages and uses AI to extract content</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f399.png" alt="🎙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Audio transcription</strong> — turn audio files into Markdown</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
services.AddMarkItDotNetAI(options =&gt;
{
    options.ImagePrompt = &quot;Describe this image in detail&quot;;
    options.AudioPrompt = &quot;Transcribe this audio&quot;;
});
</pre></div>


<pre class="wp-block-preformatted">Works with <strong>OpenAI</strong>, <strong>Azure OpenAI</strong>, or any <code>IChatClient</code> implementation. Your choice.</pre>



<p class="wp-block-paragraph">And if you want <strong>local audio transcription</strong> with zero cloud dependency? There&#8217;s <code>ElBruno.MarkItDotNet.Whisper</code> for that. </p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> URL to Markdown<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-url-to-markdown"></a></h2>



<p class="wp-block-paragraph">One more thing my friend Hector suggested &gt;&gt; converting web pages:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var service = new MarkdownService(registry);
var result = await service.ConvertUrlAsync("https://example.com");
Console.WriteLine(result.Markdown);
</pre></div>


<p class="wp-block-paragraph">Super handy for web scraping, research pipelines, or just saving articles as Markdown.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50c.png" alt="🔌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Build Your Own Converters<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-build-your-own-converters"></a></h2>



<p class="wp-block-paragraph">Don&#8217;t see your format? No problem. Implement <code>IMarkdownConverter</code> and plug it in:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class MyCustomConverter : IMarkdownConverter
{
    public string&#91;] SupportedExtensions =&gt; &#91;&quot;.custom&quot;];

    public Task&lt;ConversionResult&gt; ConvertAsync(Stream stream, string extension)
    {
        // your conversion logic here
    }
}
</pre></div>


<p class="wp-block-paragraph">Or bundle multiple converters into a plugin with&nbsp;<code>IConverterPlugin</code>. The architecture is designed to be extended.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3ae.png" alt="🎮" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 18 Sample Apps<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-18-sample-apps"></a></h2>



<p class="wp-block-paragraph">Yes, <strong>18 samples</strong>. I went a bit overboard <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> (not me, Copilot, you know what I mean):</p>



<ul class="wp-block-list">
<li><strong>BasicConversion</strong> — text, JSON, HTML</li>



<li><strong>PdfConversion</strong> — PDF + streaming</li>



<li><strong>DocxConversion</strong> — Word documents</li>



<li><strong>ExcelConversion</strong> — spreadsheets</li>



<li><strong>PowerPointConversion</strong> — slides</li>



<li><strong>AiImageDescription</strong> — AI image analysis</li>



<li><strong>WhisperTranscription</strong> — local audio</li>



<li><strong>MarkItDotNet.WebApi</strong> — minimal API with uploads + SSE</li>



<li><strong>BatchProcessor</strong> — folder batch conversion</li>



<li><strong>RagPipeline</strong> — RAG ingestion pipeline</li>



<li>…and more!</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Final Thoughts<a href="https://github.com/elbruno/ElBruno.MarkItDotNet/blob/main/docs/blog-post-markit-dotnet.md#-final-thoughts"></a></h2>



<p class="wp-block-paragraph">This project started because I needed a <strong>clean, extensible way to convert files to Markdown in .NET</strong> — especially for AI workflows. Python had <code>markitdown</code>, but .NET didn&#8217;t have a good equivalent. So I built some pet projects and they were on my personal toolbox for a while. </p>



<p class="wp-block-paragraph">Then someone ask a question, and <a href="https://www.youtube.com/watch?v=lX1wtTgCXNs" target="_blank" rel="noreferrer noopener">put a Squad to package everything</a>.</p>



<p class="wp-block-paragraph">Currently supports <strong>15+ file formats</strong>, has <strong>streaming APIs</strong>, plays nice with <strong>dependency injection</strong>, and can even use <strong>AI for OCR and transcription</strong>. Plus, it&#8217;s open source and ready for your PRs. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>NuGet:</strong> <a href="https://www.nuget.org/packages/ElBruno.MarkItDotNet">ElBruno.MarkItDotNet</a> </p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Repo:</strong> <a href="https://github.com/elbruno/ElBruno.MarkItDotNet">https://github.com/elbruno/ElBruno.MarkItDotNet</a></p>



<p class="wp-block-paragraph">If you try it, let me know what you build! <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f64c.png" alt="🙌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/04/%f0%9f%93%9d-convert-anything-to-markdown-in-net-meet-elbruno-markitdotnet/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39420</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png">
			<media:title type="html">Designer_blog_16-9</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/designer_blog_16-9.png?w=1024"/>
	</item>
		<item>
		<title>&#127775; Gemma 4 Is Here — And My C# Library Is (Almost) Ready</title>
		<link>https://elbruno.com/2026/04/03/%f0%9f%8c%9f-gemma-4-is-here-and-my-c-library-is-almost-ready/</link>
					<comments>https://elbruno.com/2026/04/03/%f0%9f%8c%9f-gemma-4-is-here-and-my-c-library-is-almost-ready/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Fri, 03 Apr 2026 21:05:08 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Gemma]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[LocalLLMs]]></category>
		<category><![CDATA[NuGet]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39413</guid>

					<description><![CDATA[Hi! So Google just dropped Gemma 4 — their most capable open model family yet — and I couldn&#8217;t resist. I spent a good chunk of time digging into the architecture, trying to convert models, hitting walls, finding workarounds, and hitting more walls. Here&#8217;s where things stand with ElBruno.LocalLLMs. Spoiler: the library is ready for Gemma 4. The ONNX runtime&#8230; [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">So Google just dropped <strong>Gemma 4</strong> — their most capable open model family yet — and I couldn&#8217;t resist. I spent a good chunk of time digging into the architecture, trying to convert models, hitting walls, finding workarounds, and hitting more walls. Here&#8217;s where things stand with <strong><a href="https://www.nuget.org/packages/ElBruno.LocalLLMs" target="_blank" rel="noreferrer noopener">ElBruno.LocalLLMs</a></strong>.</p>



<p class="wp-block-paragraph">Spoiler: the library is <em>ready</em> for Gemma 4. The ONNX runtime&#8230; not yet. So, let me tell you the whole story.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Wait, What&#8217;s Gemma 4?<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#wait-whats-gemma-4"></a></h2>



<p class="wp-block-paragraph">Google released four new models on April 2, 2026, and they&#8217;re pretty wild:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Model</th><th>Parameters</th><th>What&#8217;s Cool</th><th>Context</th></tr></thead><tbody><tr><td><strong>E2B IT</strong></td><td>5.1B (only 2.3B active!)</td><td>Tiny but punches above its weight</td><td>128K</td></tr><tr><td><strong>E4B IT</strong></td><td>8B (4.5B active)</td><td>Sweet spot for most use cases</td><td>128K</td></tr><tr><td><strong>26B A4B IT</strong></td><td>25.2B (3.8B active)</td><td>MoE — only fires 3.8B params per token <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f92f.png" alt="🤯" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>256K</td></tr><tr><td><strong>31B IT</strong></td><td>30.7B</td><td>The big one, dense, no tricks</td><td>256K</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">The magic sauce is something called&nbsp;<strong>Per-Layer Embeddings (PLE)</strong>&nbsp;— basically, each transformer layer gets its own little embedding input. That&#8217;s how a 5.1B model acts like a 2.3B one. Clever stuff.</p>



<p class="wp-block-paragraph">They&#8217;re all <strong>Apache 2.0</strong>. No gating, no license hoops. I like that.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What I Got Working (v0.8.0)<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#what-i-got-working-v080"></a></h2>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Model Definitions — Done<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#-model-definitions--done"></a></h3>



<p class="wp-block-paragraph">All four Gemma 4 variants are registered and ready to go:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var options = new LocalLLMsOptions
{
    Model = KnownModels.Gemma4E2BIT  // Smallest, edge-optimized
};
</pre></div>


<p class="wp-block-paragraph">I added&nbsp;<code>Gemma4E2BIT</code>,&nbsp;<code>Gemma4E4BIT</code>,&nbsp;<code>Gemma4_26BA4BIT</code>, and&nbsp;<code>Gemma4_31BIT</code>. The moment ONNX models exist, you just point and shoot.</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Chat Template — Already Works<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#-chat-template--already-works"></a></h3>



<p class="wp-block-paragraph">Here&#8217;s the fun part: Gemma 4 uses the&nbsp;<strong>exact same chat template</strong>&nbsp;as Gemma 2 and 3:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-apl"><div class="cm-line"><span class="tok-variableName">&lt;</span><span class="tok-keyword">start_of_turn</span><span class="tok-variableName">&gt;</span><span class="tok-keyword">user</span></div><div class="cm-line"><span class="tok-keyword">What</span> <span class="tok-keyword">is</span> <span class="tok-keyword">the</span> <span class="tok-keyword">capital</span> <span class="tok-keyword">of</span> <span class="tok-keyword">France</span><span class="tok-variableName">?&lt;</span><span class="tok-keyword">end_of_turn</span><span class="tok-variableName">&gt;</span></div><div class="cm-line"><span class="tok-variableName">&lt;</span><span class="tok-keyword">start_of_turn</span><span class="tok-variableName">&gt;</span><span class="tok-keyword">model</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">My existing&nbsp;<code>GemmaFormatter</code>&nbsp;handles it perfectly. Zero code changes needed. System messages fold into the first user turn, tool calling works — the whole thing just&#8230; works. I love when that happens.</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tool Calling — Yep, That Too<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#-tool-calling--yep-that-too"></a></h3>



<p class="wp-block-paragraph">Gemma 4 natively supports function calling, and my formatter already handles the Gemma tool-calling format with proper JSON function definitions. No changes needed.</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tests — A Lot of Them<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#-tests--a-lot-of-them"></a></h3>



<p class="wp-block-paragraph">I went a bit overboard here (no regrets, and thanks Copilot!):</p>



<ul class="wp-block-list">
<li><strong>6 model definition tests</strong> — making sure all four variants are correctly registered</li>



<li><strong>9 tool-calling tests</strong> — validating function calling scenarios with Gemma 4</li>



<li><strong>195 multilingual tests</strong> — this one deserves its own section (see below)</li>
</ul>



<p class="wp-block-paragraph">All&nbsp;<strong>697 tests</strong>&nbsp;pass. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Conversion Scripts — Ready and Waiting<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#-conversion-scripts--ready-and-waiting"></a></h3>



<p class="wp-block-paragraph">I wrote dedicated Python and PowerShell conversion scripts:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
python scripts/convert_gemma4.py --model-size e2b --output-dir ./models/gemma4-e2b
</pre></div>


<p class="wp-block-paragraph">They&#8217;re ready. They just need a runtime that can handle Gemma 4. Which brings me to&#8230;</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/23f3.png" alt="⏳" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Honest Part: ONNX Conversion Is Blocked <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f614.png" alt="😔" class="wp-smiley" style="height: 1em; max-height: 1em;" /><a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#-the-honest-part-onnx-conversion-is-blocked"></a></h2>



<p class="wp-block-paragraph">OK, here&#8217;s where I hit a wall. <strong>The ONNX conversion doesn&#8217;t work yet.</strong> <br />( I maybe missing something here, but hey, it&#8217;s a long weekend !)</p>



<h3 class="wp-block-heading">What&#8217;s the Problem?<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#whats-the-problem"></a></h3>



<p class="wp-block-paragraph">Gemma 4 has three architectural features that&nbsp;<code>onnxruntime-genai</code>&nbsp;v0.12.2 simply doesn&#8217;t support:</p>



<ol class="wp-block-list">
<li><strong>Per-Layer Embeddings (PLE)</strong> — each layer needs a separate <code>per_layer_inputs</code> tensor. The runtime expects one embedding output. Not three dozen.</li>



<li><strong>Variable Head Dimensions</strong> — sliding attention layers use <code>head_dim=256</code>, full attention layers (every 5th one) use <code>512</code>. The runtime config only has ONE <code>head_size</code> field. Pick one? Yeah, no.</li>



<li><strong>KV Cache Sharing</strong> — 35 layers share only 15 unique KV cache pairs. The runtime expects a 1:1 mapping. Math doesn&#8217;t math.</li>
</ol>



<h3 class="wp-block-heading">What I Tried (The Fun Part)<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#what-i-tried-the-fun-part"></a></h3>



<p class="wp-block-paragraph">Here&#8217;s my adventure:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Patched the GenAI builder</strong> to route Gemma 4 through the Gemma 3 pipeline — it actually produced a 1.6GB ONNX file! But then the runtime choked with a shape mismatch at the full attention layers. So close.</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Examined the onnx-community models</strong> — they have the right structure, but the I/O format is incompatible with GenAI&#8217;s KV cache management.</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9ea.png" alt="🧪" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Tried loading as <code>Gemma4ForCausalLM</code></strong> — nope, weights are stored under a multimodal prefix. Mismatch everywhere.</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50e.png" alt="🔎" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Searched for pre-release builds</strong> — nothing. 0.12.2 is the latest.</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Checked GitHub issues/PRs</strong> — zero Gemma 4 mentions in the repo.</li>
</ul>



<h3 class="wp-block-heading">So When Will It Work?<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#so-when-will-it-work"></a></h3>



<p class="wp-block-paragraph">The moment&nbsp;<code>onnxruntime-genai</code>&nbsp;adds Gemma 4 support, I&#8217;m ready to go:</p>



<ul class="wp-block-list">
<li>Model definitions <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>Chat template <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>Tests <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>Conversion scripts <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>Documentation <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>



<p class="wp-block-paragraph">I&#8217;m watching: <a href="https://github.com/microsoft/onnxruntime-genai/releases" target="_blank" rel="noreferrer noopener">microsoft/onnxruntime-genai releases</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Bonus: I Went Multilingual<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#bonus-i-went-multilingual"></a></h2>



<p class="wp-block-paragraph">While I was in testing mode, I figured — why not make sure all my formatters handle every language properly? So I added&nbsp;<strong>195 multilingual tests</strong>&nbsp;covering:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Script/Language</th><th>Examples</th></tr></thead><tbody><tr><td>CJK</td><td>日本語, 中文, 한국어</td></tr><tr><td>Cyrillic</td><td>Русский</td></tr><tr><td>Arabic</td><td>العربية (RTL)</td></tr><tr><td>Hebrew</td><td>עברית (RTL)</td></tr><tr><td>Devanagari</td><td>हिन्दी</td></tr><tr><td>Tamil</td><td>தமிழ்</td></tr><tr><td>Thai</td><td>ไทย</td></tr><tr><td>European</td><td>Ñ, Ü, Ø, Ž, Ą</td></tr><tr><td>Emoji</td><td><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" />, <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" />, <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f30d.png" alt="🌍" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td></tr><tr><td>Zero-width</td><td>ZWJ, ZWNJ characters</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">All&nbsp;<strong>7 formatters</strong>&nbsp;(ChatML, Phi3, Llama3, Qwen, Mistral, Gemma, DeepSeek) handle Unicode correctly. If you&#8217;re running models locally, you probably care about this. I know I do.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Try It Out<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#try-it-out"></a></h2>



<p class="wp-block-paragraph">Grab v0.8.0:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
dotnet add package ElBruno.LocalLLMs --version 0.8.0
</pre></div>


<p class="wp-block-paragraph">Gemma 4 ONNX models aren&#8217;t ready yet, but there are <strong>25+ other models</strong> that work right now:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.LocalLLMs;
using Microsoft.Extensions.AI;

// Gemma 2 works great today
var options = new LocalLLMsOptions { Model = KnownModels.Gemma2_2BIT };
using var client = await LocalChatClient.CreateAsync(options);
var response = await client.GetResponseAsync(&#91;
    new(ChatRole.User, "Tell me about Gemma 4!")
]);
Console.WriteLine(response.Text);
</pre></div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Links<a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blog-gemma4-support.md#links"></a></h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://www.nuget.org/packages/ElBruno.LocalLLMs">NuGet Package</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d6.png" alt="📖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/supported-models.md">Supported Models</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/onnx-conversion.md">ONNX Conversion Guide</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f6ab.png" alt="🚫" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/blocked-models.md">Blocked Models Reference</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://blog.google/innovation-and-ai/technology/developers-tools/gemma-4/">Google Gemma 4 Announcement</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f419.png" alt="🐙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/ElBruno.LocalLLMs">GitHub Repository</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/04/03/%f0%9f%8c%9f-gemma-4-is-here-and-my-c-library-is-almost-ready/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39413</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_emuwsbemuwsbemuw.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/04/gemini_generated_image_emuwsbemuwsbemuw.png">
			<media:title type="html">Gemini_Generated_Image_emuwsbemuwsbemuw</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#128640; Build QR Codes in .NET FAST with ElBruno.QRCodeGenerator</title>
		<link>https://elbruno.com/2026/03/30/%f0%9f%9a%80-build-qr-codes-in-net-fast-with-elbruno-qrcodegenerator/</link>
					<comments>https://elbruno.com/2026/03/30/%f0%9f%9a%80-build-qr-codes-in-net-fast-with-elbruno-qrcodegenerator/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[QR]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39394</guid>

					<description><![CDATA[Hi! I needed a quick way to generate QR codes in .NET—no heavy dependencies, no complex setup, just something that works and that’s exactly why I built: 👉 ElBruno.QRCodeGenerator An open-source, lightweight library to generate QR codes in multiple formats: 🎬 Watch the video I recorded a quick demo showing how everything works end-to-end 👇 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" width="688" height="384" src="https://elbruno.com/wp-content/uploads/2026/03/blog-03.png" alt="" class="wp-image-39399" srcset="https://elbruno.com/wp-content/uploads/2026/03/blog-03.png 688w, https://elbruno.com/wp-content/uploads/2026/03/blog-03.png?w=150&amp;h=84 150w, https://elbruno.com/wp-content/uploads/2026/03/blog-03.png?w=300&amp;h=167 300w" sizes="(max-width: 688px) 100vw, 688px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">I needed a <strong>quick way to generate QR codes in .NET</strong>—no heavy dependencies, no complex setup, just something that works and that’s exactly why I built:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>ElBruno.QRCodeGenerator</strong></p>



<p class="wp-block-paragraph">An open-source, lightweight library to generate QR codes in multiple formats:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5a5.png" alt="🖥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASCII (perfect for CLI tools)</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5bc.png" alt="🖼" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Images (PNG/SVG)</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Payloads (WiFi, URL, email, vCard)</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3ac.png" alt="🎬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Watch the video</h2>



<p class="wp-block-paragraph">I recorded a quick demo showing how everything works end-to-end <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/op1dYj8Af6U?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Getting started</h2>



<p class="wp-block-paragraph">The goal is simple: <strong>generate QR codes with minimal code</strong>.</p>



<p class="wp-block-paragraph">Here’s a basic example from the repo:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.QRCodeGenerator.CLI;

// Simplest possible usage
QRCode.Print("https://github.com/elbruno");
</pre></div>


<p class="wp-block-paragraph">That’s it. You already have a QR code printed directly in your terminal <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br />Perfect for CLI tools or quick debugging scenarios.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Working with Payloads</h2>



<p class="wp-block-paragraph">You can also generate <strong>real-world QR codes</strong>, not just plain text.</p>



<p class="wp-block-paragraph">For example, WiFi credentials and more:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.QRCodeGenerator.Payloads;

// WiFi network QR code
var wifi = PayloadBuilder.Wifi("MyNetwork", "MyPassword123");
Console.WriteLine(wifi.GetPayloadString());
// Output: WIFI:T:WPA;S:MyNetwork;P:MyPassword123;;

// vCard contact
var vcard = PayloadBuilder.VCard("Bruno Capuano")
    .WithPhone("+1234567890", VCardPhoneType.Mobile)
    .WithEmail("bruno@example.com", VCardEmailType.Work)
    .WithOrganization("Contoso");
Console.WriteLine(vcard.GetPayloadString());

// Combine with any renderer (e.g., CLI)
QRCode.Print(wifi.GetPayloadString());
</pre></div>


<p class="wp-block-paragraph">Other supported payloads:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4f6.png" alt="📶" class="wp-smiley" style="height: 1em; max-height: 1em;" /> WiFi</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e7.png" alt="📧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Email</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> URLs</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f464.png" alt="👤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> vCard (contacts)</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Final thoughts</h2>



<p class="wp-block-paragraph">This project started as a <strong>fun experiment (with a bit of Copilot help <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f60e.png" alt="😎" class="wp-smiley" style="height: 1em; max-height: 1em;" />)</strong> and ended up being a super handy tool for day-to-day dev work.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Repo: <a href="https://github.com/elbruno/ElBruno.QRCodeGenerator">https://github.com/elbruno/ElBruno.QRCodeGenerator</a><br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Video: <a href="https://youtu.be/op1dYj8Af6U">https://youtu.be/op1dYj8Af6U</a></p>



<p class="wp-block-paragraph">If you try it, let me know what you build! <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/30/%f0%9f%9a%80-build-qr-codes-in-net-fast-with-elbruno-qrcodegenerator/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39394</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/blog-03.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/blog-03.png">
			<media:title type="html">blog-03</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>Stop Wasting Tokens: Smart Tool Routing for LLMs with MCPToolRouter</title>
		<link>https://elbruno.com/2026/03/27/stop-wasting-tokens-smart-tool-routing-for-llms-with-mcptoolrouter/</link>
					<comments>https://elbruno.com/2026/03/27/stop-wasting-tokens-smart-tool-routing-for-llms-with-mcptoolrouter/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Fri, 27 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[LLMs]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[NuGet]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39401</guid>

					<description><![CDATA[Hi! Today I want to share something that&#8217;s been hearing a couple of times: You know when you&#8217;re building an AI agent or working with LLMs, and you have dozens (or hundreds) of tools available? What do you do? Send ALL of them to the LLM every single time, right? Yeah, me too. And it&#8217;s [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="571" src="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png?w=1024" alt="" class="wp-image-39409" srcset="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Today I want to share something that&#8217;s been hearing a couple of times: You know when you&#8217;re building an AI agent or working with LLMs, and you have dozens (or hundreds) of tools available? What do you do? Send ALL of them to the LLM every single time, right?</p>



<p class="wp-block-paragraph">Yeah, me too. And it&#8217;s expensive. I had as a pet project for a while, so I decided to finish it, and start to give a real try. Let&#8217;s move on.</p>



<h2 class="wp-block-heading">The Token Problem Nobody Talks About <a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#the-token-problem-nobody-talks-about"></a></h2>



<p class="wp-block-paragraph">TL;DR: every tool you send to an LLM costs tokens. Not just tool names, descriptions, and full JSON schemas. With 50+ tools, you can easily burn through 2,000+ tokens&nbsp;<strong>before the LLM even thinks about your question</strong>.</p>



<p class="wp-block-paragraph">And that&#8217;s just non-optimal. IE: if I&#8217;m asking &#8220;What&#8217;s the weather in Toronto?&#8221;, why am I sending 47 other tools about databases, emails, and file systems?</p>



<h2 class="wp-block-heading">Enter MCPToolRouter<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#enter-mcptoolrouter"></a></h2>



<p class="wp-block-paragraph">So I built something to fix this:&nbsp;<strong>ElBruno.ModelContextProtocol.MCPToolRouter</strong>. A .NET library that uses semantic search to route your prompts to the most relevant tools. Think of it as a smart filter that sits between your user&#8217;s question and your LLM.</p>



<p class="wp-block-paragraph">Here&#8217;s how it works:</p>



<ol class="wp-block-list">
<li><strong>Index your tools once</strong>&nbsp;(using local embeddings—no API calls)</li>



<li><strong>Search semantically</strong>&nbsp;when a user asks something</li>



<li><strong>Get back only the relevant tools</strong>&nbsp;(top 3, top 5, whatever you need)</li>



<li><strong>Send those to your LLM</strong>&nbsp;instead of everything</li>
</ol>



<p class="wp-block-paragraph">The result?&nbsp;<strong>70-80% token savings</strong>&nbsp;in my testing scenarios. </p>



<h2 class="wp-block-heading">Show Me the Code<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#show-me-the-code"></a></h2>



<p class="wp-block-paragraph">Let&#8217;s get practical. Here&#8217;s the simplest possible example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.ModelContextProtocol.MCPToolRouter;
using ModelContextProtocol.Protocol;

// Define your MCP tools (or pull them from an MCP server)
var tools = new&#91;]
{
    new Tool { Name = "get_weather", Description = "Get weather for a location" },
    new Tool { Name = "send_email", Description = "Send an email message" },
    new Tool { Name = "search_files", Description = "Search files by name or content" },
    new Tool { Name = "calculate", Description = "Perform mathematical calculations" }
};

// Create the index (one-time cost)
await using var index = await ToolIndex.CreateAsync(tools);

// Find the most relevant tools for a prompt
var results = await index.SearchAsync("What's the temperature outside?", topK: 3);

foreach (var r in results)
    Console.WriteLine($"{r.Tool.Name}: {r.Score:F3}");
</pre></div>


<pre class="wp-block-preformatted"><strong>Output:</strong></pre>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">get_weather: 0.892</div><div class="cm-line">search_files: 0.234</div><div class="cm-line">calculate: 0.187</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">See that? It knows&nbsp;<code>get_weather</code>&nbsp;is the right tool. Now you send just that one (or top 3) to your LLM instead of all 50.</p>



<h2 class="wp-block-heading">Real-World Integration with Azure OpenAI<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#real-world-integration-with-azure-openai"></a></h2>



<p class="wp-block-paragraph">Here&#8217;s where it gets practical. Let&#8217;s say you&#8217;re using Azure OpenAI and want to save tokens:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Create Azure OpenAI client
var chatClient = new AzureOpenAIClient(
    new Uri("https://your-resource.openai.azure.com/"),
    new AzureKeyCredential("your-api-key"))
    .GetChatClient("gpt-5-mini");

// Route to relevant tools only
var userPrompt = "What's the weather in Seattle?";
var relevant = await index.SearchAsync(userPrompt, topK: 3);

// Add only filtered tools to the chat call — saving tokens!
var chatOptions = new ChatCompletionOptions();
foreach (var r in relevant)
    chatOptions.Tools.Add(ChatTool.CreateFunctionTool(r.Tool.Name, r.Tool.Description ?? ""));

var response = await chatClient.CompleteChatAsync(
    &#91;new UserChatMessage(userPrompt)],
    chatOptions);
</pre></div>


<p class="wp-block-paragraph">Instead of sending 50 tools (2,000 tokens), you send 3 tools (~300 tokens). That&#8217;s an 85% reduction. Multiply that by thousands of API calls, and <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>



<h2 class="wp-block-heading">The Best Part: It&#8217;s All Local<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#the-best-part-its-all-local"></a></h2>



<p class="wp-block-paragraph">Here&#8217;s what I love about this:&nbsp;<strong>no external API calls for embeddings</strong>. MCPToolRouter uses local ONNX models (via my&nbsp;<a href="https://github.com/elbruno/ElBruno.LocalEmbeddings">ElBruno.LocalEmbeddings</a>&nbsp;library), so everything runs on your machine or server. Fast, private, and cost-effective.</p>



<p class="wp-block-paragraph">First run downloads a small embedding model (~25 MB), and after that, it&#8217;s instant.</p>



<h2 class="wp-block-heading">Advanced Features<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#advanced-features"></a></h2>



<p class="wp-block-paragraph">Once you get the basics, there&#8217;s more:</p>



<h3 class="wp-block-heading">Save/Load Indexes<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#saveload-indexes"></a></h3>



<p class="wp-block-paragraph">Pre-build your index and load it instantly on startup:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Save
using var file = File.Create("tools.bin");
await index.SaveAsync(file);

// Load (instant warm-start)
using var stream = File.OpenRead("tools.bin");
await using var loaded = await ToolIndex.LoadAsync(stream);
</pre></div>


<h3 class="wp-block-heading">Dynamic Updates<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#dynamic-updates"></a></h3>



<p class="wp-block-paragraph">Add or remove tools at runtime without rebuilding everything:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
await index.AddToolsAsync(new&#91;] { new Tool { Name = "new_tool", Description = "..." } });
index.RemoveTools(new&#91;] { "obsolete_tool" });
</pre></div>


<h3 class="wp-block-heading">Dependency Injection<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#dependency-injection"></a></h3>



<p class="wp-block-paragraph">Works great with ASP.NET Core:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
builder.Services.AddMcpToolRouter(tools, opts =&gt;
{
    opts.QueryCacheSize = 20; // LRU cache for repeated queries
});

// Inject IToolIndex anywhere
app.MapGet(&quot;/search&quot;, async (IToolIndex index, string query)
    =&gt; await index.SearchAsync(query, topK: 3));
</pre></div>


<h2 class="wp-block-heading">Try It Yourself<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#try-it-yourself"></a></h2>



<p class="wp-block-paragraph">The library is available on NuGet and fully open source:</p>



<pre class="wp-block-preformatted">dotnet add package ElBruno.ModelContextProtocol.MCPToolRouter</pre>



<p class="wp-block-paragraph">I&#8217;ve also included&nbsp;<strong>6 sample applications</strong>&nbsp;that show different use cases:</p>



<ul class="wp-block-list">
<li><strong>BasicUsage</strong>: Getting started with tool indexing and search</li>



<li><strong>TokenComparison</strong>: Side-by-side comparison showing token savings</li>



<li><strong>TokenComparisonMax</strong>: Extreme scenario with 120+ tools</li>



<li><strong>FilteredFunctionCalling</strong>: End-to-end function calling with filtered tools</li>



<li><strong>AgentWithToolRouter</strong>: Integration with Microsoft Agent Framework</li>



<li><strong>FunctionalToolsValidation</strong>: 52 real tools with execution validation</li>
</ul>



<p class="wp-block-paragraph">Check out the full repo on GitHub: <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://github.com/elbruno/ElBruno.ModelContextProtocol">https://github.com/elbruno/ElBruno.ModelContextProtocol</a></strong></p>



<h2 class="wp-block-heading">Why This Matters<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#why-this-matters"></a></h2>



<p class="wp-block-paragraph">Look, I&#8217;m not saying you should&nbsp;<strong>always</strong>&nbsp;use semantic routing. If you only have 5-10 tools, sending them all is fine. But once you cross into dozens or hundreds of tools, the cost and context window bloat become real problems.</p>



<p class="wp-block-paragraph">MCPToolRouter is an approach that try to solve this with a simple, pragmatic approach:&nbsp;<strong>send only what matters</strong>. </p>



<h2 class="wp-block-heading">What&#8217;s Next?<a href="https://github.com/elbruno/ElBruno.ModelContextProtocol/blob/f892bcbd0f8eac90a9ddf7e44cb9ed7dba605c77/docs/blog-post.md#whats-next"></a></h2>



<p class="wp-block-paragraph">If I see the need and some real traction, I&#8217;ll start working on this library more and more, and I&#8217;d love your feedback. </p>



<p class="wp-block-paragraph">Try it out, break it, and let me know what you think. Open issues, send PRs, or just drop a comment.</p>



<p class="wp-block-paragraph">And if you find it useful, star the repo and share it with your team. </p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph"><em>P.S. — The TokenComparisonMax sample has a beautiful Spectre.Console UI that shows live token savings. It&#8217;s oddly satisfying to watch. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></em></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/27/stop-wasting-tokens-smart-tool-routing-for-llms-with-mcptoolrouter/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39401</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png">
			<media:title type="html">Gemini_Generated_Image_dyudd1dyudd1dyud</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_dyudd1dyudd1dyud.png?w=1024"/>
	</item>
		<item>
		<title>Build Your Own AI Agent Platform with .NET and GitHub Copilot – OpenClawNet – Free Live Series at Microsoft Reactor</title>
		<link>https://elbruno.com/2026/03/26/build-your-own-ai-agent-platform-with-net-and-github-copilot-openclawnet-free-live-series-at-microsoft-reactor/</link>
					<comments>https://elbruno.com/2026/03/26/build-your-own-ai-agent-platform-with-net-and-github-copilot-openclawnet-free-live-series-at-microsoft-reactor/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 26 Mar 2026 13:01:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<category><![CDATA[Microsoft Reactor]]></category>
		<category><![CDATA[Series]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39387</guid>

					<description><![CDATA[Hi! Are you ready to dive into the world of AI agents? Join us for an exciting four-session live series at Microsoft Reactor where we&#8217;ll build OpenClawNet, a production-grade AI agent platform from scratch using .NET 10 and GitHub Copilot. What You&#8217;ll Learn Over four interactive sessions, we&#8217;ll transform a simple chatbot into an intelligent, autonomous [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="682" src="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=1024" alt="" class="wp-image-39391" srcset="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png 1536w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Are you ready to dive into the world of AI agents? Join us for an exciting four-session live series at Microsoft Reactor where we&#8217;ll build <strong>OpenClawNet</strong>, a production-grade AI agent platform from scratch using .NET 10 and GitHub Copilot.</p>



<h2 class="wp-block-heading">What You&#8217;ll Learn<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#what-youll-learn"></a></h2>



<p class="wp-block-paragraph">Over four interactive sessions, we&#8217;ll transform a simple chatbot into an intelligent, autonomous agent capable of reasoning through complex tasks, calling tools, remembering context, and scaling to the cloud.</p>



<h3 class="wp-block-heading">Here&#8217;s What We&#8217;re Building Together:<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#heres-what-were-building-together"></a></h3>



<p class="wp-block-paragraph"><strong>Session 1: Foundation &amp; Local Chat</strong> (60 minutes) We kick off by scaffolding a complete .NET 10 solution with Aspire orchestration, Minimal APIs, SignalR for real-time communication, and a Blazor web UI. By the end, you&#8217;ll have a working local chatbot powered by Ollama or Foundry Local, complete with persistent chat history using SQLite and EF Core.</p>



<p class="wp-block-paragraph"><strong>Session 2: Intelligence &amp; Tools</strong>&nbsp;(60 minutes) Transform the chatbot into an agent that can actually do things. We&#8217;ll implement a tool framework with security guardrails, enabling your agent to access files, run commands, fetch web content, and schedule tasks. Watch as your agent reasons through multi-step problems autonomously.</p>



<p class="wp-block-paragraph"><strong>Session 3: Skills &amp; Memory</strong>&nbsp;(60 minutes) Personalize your agent with reusable skill bundles defined in Markdown. Add conversation memory and automatic summarization to manage token budgets. Your users will be able to toggle skills like &#8220;Git Expert&#8221; or &#8220;JavaScript Mentor&#8221; without touching a line of code.</p>



<p class="wp-block-paragraph"><strong>Session 4: Production Ready</strong>&nbsp;(60 minutes) Take it to production with job scheduling, Azure OpenAI integration, Microsoft Foundry, monitoring dashboards, and comprehensive unit tests. Deploy to Azure Container Apps or run on-premises—your choice.</p>



<h2 class="wp-block-heading">Why Join This Series?<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#why-join-this-series"></a></h2>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Hands-On Learning</strong>: Every session includes working code you can follow along with, inspect, and extend </p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>See Copilot in Action</strong>: Watch how GitHub Copilot accelerates real-world development </p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Production-Grade Patterns</strong>: Learn architecture patterns you can apply immediately to your own projects </p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4ac.png" alt="💬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Interactive &amp; Live</strong>: Ask questions in real-time and engage with the community </p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Access Recordings</strong>: Can&#8217;t make it live? All sessions are recorded with supplementary guides</p>



<h2 class="wp-block-heading">Who Should Attend?<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#who-should-attend"></a></h2>



<p class="wp-block-paragraph">This series is perfect for:</p>



<ul class="wp-block-list">
<li>.NET developers exploring AI capabilities</li>



<li>Enterprise architects planning AI features for their applications</li>



<li>GitHub Copilot enthusiasts who want to see it tackle complex, real-world scenarios</li>



<li>Anyone curious about how modern AI agents actually work under the hood</li>
</ul>



<h2 class="wp-block-heading">Prerequisites<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#prerequisites"></a></h2>



<p class="wp-block-paragraph">You&#8217;ll need intermediate C# and ASP.NET Core knowledge, the .NET 10 SDK, VS Code with GitHub Copilot (or Visual Studio), and Docker for local Ollama. An Azure subscription is optional for sessions 3-4, but everything works locally too.</p>



<h2 class="wp-block-heading">The Technologies we&#8217;ll cover<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#the-technologies-youll-master"></a></h2>



<ul class="wp-block-list">
<li><strong>.NET 10</strong> with Blazor, Minimal APIs, and SignalR</li>



<li><strong>Aspire</strong> for service orchestration</li>



<li><strong>Ollama</strong> and Foundry Local for local LLM inference</li>



<li><strong>Azure OpenAI</strong> and <strong>Microsoft Foundry</strong> for cloud-scale AI</li>



<li><strong>GitHub Copilot</strong> as your AI coding partner</li>



<li>Microsoft Agent Framework to create and orchestrate agents</li>



<li><strong>EF Core</strong> with SQLite for data persistence</li>
</ul>



<h2 class="wp-block-heading">Let&#8217;s do this!<a href="https://github.com/elbruno/tuis/blob/main/docs/reactor/blog-post.md#ready-to-build-the-future"></a></h2>



<p class="wp-block-paragraph"><strong>Join the series now and let&#8217;s build something awesome together!</strong></p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Register here</strong>:&nbsp;<a href="https://developer.microsoft.com/en-us/reactor/series/S-1652/">https://developer.microsoft.com/en-us/reactor/series/S-1652/</a></p>



<p class="wp-block-paragraph">See you at Microsoft Reactor!</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/26/build-your-own-ai-agent-platform-with-net-and-github-copilot-openclawnet-free-live-series-at-microsoft-reactor/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39387</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png">
			<media:title type="html">ChatGPT Image Mar 25, 2026, 09_06_45 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-25-2026-09_06_45-pm.png?w=1024"/>
	</item>
		<item>
		<title>&#129302; Local LLM Chat Completions in .NET — Just C#</title>
		<link>https://elbruno.com/2026/03/19/%f0%9f%a4%96-local-llm-chat-completions-in-net-just-c/</link>
					<comments>https://elbruno.com/2026/03/19/%f0%9f%a4%96-local-llm-chat-completions-in-net-just-c/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 18:58:50 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[ChatCompletions]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[LLMs]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[technology]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=39380</guid>

					<description><![CDATA[Hi! Let&#8217;s look at this code snippet: That&#8217;s it. This runs a local LLM. No API keys. No REST calls. The model downloads automatically the first time. Let me show you more. ⬇️ Download Progress and Model Info When you run a model for the first time, you probably want to see what&#8217;s happening. Here&#8217;s [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Let&#8217;s look at this code snippet:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalLLMs</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">AI</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">client</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">LocalChatClient</span>.<span class="tok-variableName">CreateAsync</span>();</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">response</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">client</span>.<span class="tok-variableName">GetResponseAsync</span>([</div><div class="cm-line">    <span class="tok-keyword">new</span>(<span class="tok-variableName">ChatRole</span>.<span class="tok-variableName">User</span>, <span class="tok-string">&quot;What is the capital of France?&quot;</span>)</div><div class="cm-line">]);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">response</span>.<span class="tok-variableName">Text</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it. This runs a local LLM. No API keys. No REST calls. The model downloads automatically the first time.</p>



<p class="wp-block-paragraph">Let me show you more.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Download Progress and Model Info</h2>



<p class="wp-block-paragraph">When you run a model for the first time, you probably want to see what&#8217;s happening. Here&#8217;s how you track the download and check what&#8217;s loaded:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalLLMs</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">AI</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">options</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">LocalLLMsOptions</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Model</span> <span class="tok-operator">=</span> <span class="tok-variableName">KnownModels</span>.<span class="tok-variableName">Phi35MiniInstruct</span>,</div><div class="cm-line">    <span class="tok-variableName">EnsureModelDownloaded</span> <span class="tok-operator">=</span> <span class="tok-atom">true</span></div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">progress</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">Progress</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ModelDownloadProgress</span><span class="tok-operator">&gt;</span>(<span class="tok-variableName">p</span> <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">pct</span> <span class="tok-operator">=</span> <span class="tok-variableName">p</span>.<span class="tok-variableName">PercentComplete</span> <span class="tok-operator">*</span> <span class="tok-number">100</span>;</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;\r&#x2b07; {p.FileName}: {pct:F1}%&quot;</span>);</div><div class="cm-line">});</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">client</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">LocalChatClient</span>.<span class="tok-variableName">CreateAsync</span>(<span class="tok-variableName">options</span>, <span class="tok-variableName">progress</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>();</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;✓ Model ready&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;  Provider: {client.Metadata.ProviderName}&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;  Model: {client.Metadata.DefaultModelId}&quot;</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The model downloads from HuggingFace the first time you run it. After that, it&#8217;s cached locally. No more waiting.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f30a.png" alt="🌊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Streaming Responses</h2>



<p class="wp-block-paragraph">Real-time token generation works exactly how you&#8217;d expect with&nbsp;<code>IAsyncEnumerable</code>:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">update</span> <span class="tok-keyword">in</span> <span class="tok-variableName">client</span>.<span class="tok-variableName">GetStreamingResponseAsync</span>([</div><div class="cm-line">    <span class="tok-keyword">new</span>(<span class="tok-variableName">ChatRole</span>.<span class="tok-variableName">System</span>, <span class="tok-string">&quot;You are a helpful assistant.&quot;</span>),</div><div class="cm-line">    <span class="tok-keyword">new</span>(<span class="tok-variableName">ChatRole</span>.<span class="tok-variableName">User</span>, <span class="tok-string">&quot;Explain machine learning in 2 sentences.&quot;</span>)</div><div class="cm-line">]))</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">update</span>.<span class="tok-variableName">Text</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Tokens show up as they&#8217;re generated. No buffering, no polling.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Why I Built This</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Make local LLM inference feel like native .NET — not like wrapping Python or gluing REST APIs together.</p>
</blockquote>



<p class="wp-block-paragraph">Running models locally means&nbsp;<strong>privacy</strong>,&nbsp;<strong>no costs</strong>,&nbsp;<strong>low latency</strong>, and&nbsp;<strong>full control</strong>&nbsp;over your data.</p>



<p class="wp-block-paragraph">The library implements <code>IChatClient</code> from <code>Microsoft.Extensions.AI</code>. That means you can swap between Azure OpenAI, Ollama, and local models with <strong>zero code changes</strong>. Same interface, different provider.</p>



<p class="wp-block-paragraph">And there are 20+ models available, from 0.5B to 70B parameters.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> What Makes This Different?</h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>100% Local Execution</strong> — ONNX Runtime GenAI under the hood, no Python required</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>IChatClient Compatible</strong> — same interface as Azure OpenAI or Ollama</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Auto Model Management</strong> — downloads from HuggingFace, cached locally</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Multi-Model</strong> — Phi-3.5, Phi-4, Qwen2.5, Llama 3.2, Gemma, Mistral, DeepSeek</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Streaming</strong> — real-time token generation out of the box</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>DI-Ready</strong> — <code>AddLocalLLMs()</code> for ASP.NET Core integration</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Getting Started</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet add package ElBruno.LocalLLMs</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Check the&nbsp;<a href="https://www.nuget.org/packages/ElBruno.LocalLLMs">NuGet package</a>&nbsp;and the&nbsp;<a href="https://github.com/elbruno/ElBruno.LocalLLMs">GitHub repo</a>&nbsp;for samples and docs.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4ad.png" alt="💭" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Final Thoughts</h2>



<p class="wp-block-paragraph">If you can use&nbsp;<code>HttpClient</code>, you can use&nbsp;<code>LocalChatClient</code>. That&#8217;s the bar I was aiming for.</p>



<p class="wp-block-paragraph">The goal is simple: make AI accessible and natural for .NET developers. No wrappers, no workarounds — just C#.</p>



<p class="wp-block-paragraph">Happy coding!</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Resources</h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NuGet: <a href="https://www.nuget.org/packages/ElBruno.LocalLLMs">https://www.nuget.org/packages/ElBruno.LocalLLMs</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Repository: <a href="https://github.com/elbruno/ElBruno.LocalLLMs">https://github.com/elbruno/ElBruno.LocalLLMs</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f917.png" alt="🤗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> HuggingFace Models: <a href="https://huggingface.co/elbruno">https://huggingface.co/elbruno</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/19/%f0%9f%a4%96-local-llm-chat-completions-in-net-just-c/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">39380</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-19-2026-02_53_02-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/chatgpt-image-mar-19-2026-02_53_02-pm.png">
			<media:title type="html">ChatGPT Image Mar 19, 2026, 02_53_02 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#127897;️ No Tiene Nombre — Episodios NTN 474 a NTN 487</title>
		<link>https://elbruno.com/2026/03/11/%f0%9f%8e%99%ef%b8%8f-no-tiene-nombre-episodios-ntn-474-a-ntn-487/</link>
					<comments>https://elbruno.com/2026/03/11/%f0%9f%8e%99%ef%b8%8f-no-tiene-nombre-episodios-ntn-474-a-ntn-487/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 11 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[no tiene nombre]]></category>
		<category><![CDATA[ntn]]></category>
		<category><![CDATA[Podcast]]></category>
		<category><![CDATA[Spanish Post]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=38985</guid>

					<description><![CDATA[Una nueva tanda de episodios de No Tiene Nombre trae noticias sobre inteligencia artificial, tecnología y el impacto real que estas herramientas están teniendo en el mundo del desarrollo y mas. Aquí tienes un resumen rápido de cada episodio con su enlace directo. 🎧 NTN 487 Título: NTN 487 – IA y nuevas batallas tecnológicasDescripción: [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Una nueva tanda de episodios de <em>No Tiene Nombre</em> trae noticias sobre inteligencia artificial, tecnología y el impacto real que estas herramientas están teniendo en el mundo del desarrollo y mas. Aquí tienes un resumen rápido de cada episodio con su enlace directo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 487 </h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 487 – IA y nuevas batallas tecnológicas<br /><strong>Descripción:</strong> Reflexión sobre cómo los nuevos modelos y herramientas de IA están cambiando el equilibrio entre empresas tecnológicas, desarrolladores y usuarios.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/167676850">https://go.ivoox.com/rf/167676850</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 486</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 486 – IA en producción<br /><strong>Descripción:</strong> Qué ocurre cuando los modelos de IA pasan de demos a sistemas reales en producción y los retos técnicos que aparecen en ese salto.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/167621960" rel="nofollow">https://go.ivoox.com/rf/167621960</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 485</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 485 – Arquitecturas de agentes<br /><strong>Descripción:</strong> Exploración de cómo diseñar agentes inteligentes robustos, combinando skills, herramientas y orquestación adecuada.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/167403023" rel="nofollow">https://go.ivoox.com/rf/167403023</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 484</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 484 – IA y economía tecnológica<br /><strong>Descripción:</strong> Análisis del impacto económico de la IA, desde infraestructura hasta modelos de negocio y monetización.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/167333622" rel="nofollow">https://go.ivoox.com/rf/167333622</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 483</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 483 – Automatización con IA<br /><strong>Descripción:</strong> Casos prácticos donde la IA empieza a ejecutar tareas completas y los retos de seguridad y gobernanza que aparecen.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/167267629" rel="nofollow">https://go.ivoox.com/rf/167267629</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 482</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 482 – Seguridad y agentes<br /><strong>Descripción:</strong> Cómo los agentes de IA pueden abrir nuevas superficies de ataque y por qué la seguridad debe diseñarse desde el inicio.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/166337897">https://go.ivoox.com/rf/166337897</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 481</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 481 – Modelos y costos reales<br /><strong>Descripción:</strong> Los costos ocultos de operar modelos de IA a escala y cómo afectan las decisiones de arquitectura.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/166148194">https://go.ivoox.com/rf/166148194</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 480</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 480 – Herramientas y ecosistema IA<br /><strong>Descripción:</strong> Panorama de frameworks, herramientas y plataformas que están definiendo el desarrollo con IA hoy.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/165938973">https://go.ivoox.com/rf/165938973</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 479</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 479 – El futuro de los agentes<br /><strong>Descripción:</strong> Discusión sobre la evolución de los agentes autónomos y el papel que jugarán en software y negocios.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/165757342">https://go.ivoox.com/rf/165757342</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 478</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 478 – Plataformas y desarrolladores<br /><strong>Descripción:</strong> Cómo las plataformas de IA están cambiando la forma de construir aplicaciones y servicios.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/165611483">https://go.ivoox.com/rf/165611483</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 477</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 477 – Infraestructura de IA<br /><strong>Descripción:</strong> Hardware, energía y data centers: la base invisible que hace posible el boom de la inteligencia artificial.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://go.ivoox.com/rf/165304163">https://go.ivoox.com/rf/165304163</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 476</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 476 – IA en el mundo real<br /><strong>Descripción:</strong> Ejemplos concretos de cómo la IA está impactando industrias más allá del software.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/164940995" rel="nofollow">https://go.ivoox.com/rf/164940995</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 475</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 475 – El hype de la IA<br /><strong>Descripción:</strong> Separando hype de realidad en el ecosistema actual de modelos y herramientas.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/164832250" rel="nofollow">https://go.ivoox.com/rf/164832250</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 474</h2>



<p class="wp-block-paragraph"><strong>Título:</strong> NTN 474 – Tendencias de IA<br /><strong>Descripción:</strong> Un vistazo a las tendencias que están marcando el ritmo de innovación en inteligencia artificial.<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a><a href="https://go.ivoox.com/rf/164308669" rel="nofollow">https://go.ivoox.com/rf/164308669</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f399.png" alt="🎙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Escucha todos los episodios en:</strong><br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://notienenombre.com?utm_source=chatgpt.com">https://notienenombre.com</a></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/11/%f0%9f%8e%99%ef%b8%8f-no-tiene-nombre-episodios-ntn-474-a-ntn-487/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">38985</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2025/08/25-08-23-blog-post-16-9.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2025/08/25-08-23-blog-post-16-9.png">
			<media:title type="html">25 08 23 blog post 16-9</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>Agent Skills in .NET: A Minimal Working Demo</title>
		<link>https://elbruno.com/2026/03/09/agent-skills-in-net-a-minimal-working-demo/</link>
					<comments>https://elbruno.com/2026/03/09/agent-skills-in-net-a-minimal-working-demo/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<category><![CDATA[Skills]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37283</guid>

					<description><![CDATA[Hi 👋 If you’re experimenting with AI Agents in .NET, one of the concepts that appears quickly is Agent Skills. Skills are the mechanism that allows an agent to extend its capabilities: calling prompts, running code, or executing external logic. To make this easier to understand, we published a small sample repo: 👉 https://github.com/Azure-Samples/agent-skills-dotnet-demo The [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="571" src="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png?w=1024" alt="" class="wp-image-37290" srcset="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">If you’re experimenting with <strong>AI Agents in .NET</strong>, one of the concepts that appears quickly is <strong>Agent Skills</strong>.</p>



<p class="wp-block-paragraph">Skills are the mechanism that allows an agent to <strong>extend its capabilities</strong>: calling prompts, running code, or executing external logic.</p>



<p class="wp-block-paragraph">To make this easier to understand, we published a small sample repo:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/Azure-Samples/agent-skills-dotnet-demo">https://github.com/Azure-Samples/agent-skills-dotnet-demo</a></p>



<p class="wp-block-paragraph">The goal of the sample is simple: <strong>show how to register and execute skills from a .NET agent with minimal setup</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The sample app</h2>



<p class="wp-block-paragraph">The repo uses a <strong>.NET 10 file-based app</strong>, so there’s no project scaffolding required.</p>



<pre class="wp-block-preformatted">Just clone the repo, deploy the Foundry models (if you don't have them) and then run the app. </pre>



<p class="wp-block-paragraph">The demo runs&nbsp;<strong>three prompts</strong>, each triggering a different skill with inline sample data:</p>



<ol class="wp-block-list">
<li><strong>Meeting Notes</strong> — Summarizes a standup transcript into key points, decisions, and action items. This is a Prompt based skill.</li>



<li><strong>Data Analyzer</strong> — Analyzes CSV sales data for trends, top performers, and anomalies. This is a Python based skill.</li>



<li><strong>Code Reviewer</strong> — Reviews a C# code snippet for bugs, performance issues, and best practices. This is a C# based skill.</li>
</ol>



<p class="wp-block-paragraph">The&nbsp;<code>FileAgentSkillsProvider</code>&nbsp;exposes skills as tools — the model reads skill descriptions and automatically picks the best match for each prompt.</p>



<h2 class="wp-block-heading">Why skills matter</h2>



<p class="wp-block-paragraph">Agent skills are an important building block for <strong>real AI applications</strong>, because they allow agents to:</p>



<p class="wp-block-paragraph">• access external systems<br />• run deterministic code<br />• integrate with APIs<br />• orchestrate complex workflows</p>



<p class="wp-block-paragraph">Instead of a simple prompt-response loop, agents can <strong>use tools and capabilities</strong> to complete tasks.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Try it yourself</h2>



<p class="wp-block-paragraph">Clone the repo and experiment with adding new skills: <a href="https://github.com/Azure-Samples/agent-skills-dotnet-demo">https://github.com/Azure-Samples/agent-skills-dotnet-demo</a></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/09/agent-skills-in-net-a-minimal-working-demo/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37283</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png">
			<media:title type="html">Gemini_Generated_Image_spqdqjspqdqjspqd</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/gemini_generated_image_spqdqjspqdqjspqd.png?w=1024"/>
	</item>
		<item>
		<title>⏰ ClockTray – Hide or Show Your Windows Clock with One Click (yes, in C#)</title>
		<link>https://elbruno.com/2026/03/04/%e2%8f%b0-clocktray-hide-or-show-your-windows-clock-with-one-click/</link>
					<comments>https://elbruno.com/2026/03/04/%e2%8f%b0-clocktray-hide-or-show-your-windows-clock-with-one-click/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 05 Mar 2026 02:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Clock]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[System Tray]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37276</guid>

					<description><![CDATA[Sometimes you want to see the clock all the time.Sometimes you want it completely hidden. Depending on how you work, the Windows clock in the taskbar can either be useful… or distracting. So I built a tiny utility that lets you toggle the Windows clock visibility with a single click. Meet ClockTray. The Problem Windows [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<figure class="wp-block-image size-large"><img loading="lazy" width="668" height="206" src="https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif?w=668" alt="" class="wp-image-37278" srcset="https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif 668w, https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif?w=150 150w, https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif?w=300 300w" sizes="(max-width: 668px) 100vw, 668px" /></figure>



<p class="wp-block-paragraph">Sometimes you want to <strong>see the clock all the time</strong>.<br />Sometimes you want it <strong>completely hidden</strong>.</p>



<p class="wp-block-paragraph">Depending on how you work, the Windows clock in the taskbar can either be useful… or distracting. So I built a tiny utility that lets you <strong>toggle the Windows clock visibility with a single click</strong>.</p>



<p class="wp-block-paragraph">Meet <strong>ClockTray</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The Problem</h2>



<p class="wp-block-paragraph">Windows lets you show or hide the taskbar clock, but the process is not exactly a quick one.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The Idea</h2>



<p class="wp-block-paragraph"><strong>ClockTray</strong> puts a small utility in your <strong>system tray</strong> that lets you:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5b1.png" alt="🖱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Show or hide the Windows clock instantly</strong><br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Do it with <strong>one click</strong> or a keyboard shortcut<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1fab6.png" alt="🪶" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Run a <strong>tiny lightweight background tool</strong></p>



<p class="wp-block-paragraph">Simple. Fast. Done.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Why this is useful (at least for me)</h2>



<p class="wp-block-paragraph">This can be surprisingly handy in a few scenarios:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Recording demos or videos</strong> where you don’t want the clock visible</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9d1-200d-1f4bb.png" alt="🧑‍💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Presentations or livestreams</strong> where you want a clean taskbar</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Focus sessions</strong> where removing time helps reduce distractions</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5a5.png" alt="🖥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Custom desktop setups</strong> where you control exactly what appears on the taskbar</li>
</ul>



<p class="wp-block-paragraph">Instead of navigating Windows settings every time, <strong>ClockTray gives you a quick toggle</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">A tiny .NET utility</h2>



<p class="wp-block-paragraph">ClockTray is intentionally minimal. It’s a small experiment in building a <strong>simple Windows tray tool with .NET</strong>, designed to do one thing well.</p>



<p class="wp-block-paragraph">No configuration. No complex UI. Just a <strong>quick toggle for your clock</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Open Source</h2>



<p class="wp-block-paragraph">The project is open source and available here:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/elbruno/ElBruno.ClockTray">https://github.com/elbruno/ElBruno.ClockTray</a></p>



<p class="wp-block-paragraph">Feel free to explore it, modify it, or use it as inspiration for your own <strong>small Windows productivity utilities</strong>.</p>



<p class="wp-block-paragraph">Sometimes the best tools are the ones that do <strong>exactly one thing — and do it instantly</strong>.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/04/%e2%8f%b0-clocktray-hide-or-show-your-windows-clock-with-one-click/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37276</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif">
			<media:title type="html">ClockTray-Demo</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/clocktray-demo.gif?w=668"/>
	</item>
		<item>
		<title>&#127897;️&#129302; Real-Time AI Conversations in .NET — Local STT, TTS, VAD and LLM</title>
		<link>https://elbruno.com/2026/03/02/%f0%9f%8e%99%ef%b8%8f%f0%9f%a4%96-real-time-ai-conversations-in-net-local-stt-tts-vad-and-llm-no-cloud-required/</link>
					<comments>https://elbruno.com/2026/03/02/%f0%9f%8e%99%ef%b8%8f%f0%9f%a4%96-real-time-ai-conversations-in-net-local-stt-tts-vad-and-llm-no-cloud-required/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 02 Mar 2026 17:01:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Ollama]]></category>
		<category><![CDATA[Realtime Conversations]]></category>
		<category><![CDATA[technology]]></category>
		<category><![CDATA[TTS]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37266</guid>

					<description><![CDATA[Hi 👋 What if you could build a&#160;real-time voice conversation app&#160;in .NET — speech-to-text, text-to-speech, voice activity detection, and LLM responses — all running&#160;locally on your machine? That&#8217;s exactly what&#160;ElBruno.Realtime&#160;does. 🎥 Watch the full video here Why I Built This I&#8217;ve been building local AI tools for .NET for a while —&#160;local embeddings,&#160;local TTS with [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">What if you could build a&nbsp;<strong>real-time voice conversation app</strong>&nbsp;in .NET — speech-to-text, text-to-speech, voice activity detection, and LLM responses — all running&nbsp;<strong>locally on your machine</strong>? </p>



<p class="wp-block-paragraph">That&#8217;s exactly what&nbsp;<strong>ElBruno.Realtime</strong>&nbsp;does.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Watch the full video here </p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/zf3lfkeHCRA?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>



<h2 class="wp-block-heading">Why I Built This</h2>



<p class="wp-block-paragraph">I&#8217;ve been building local AI tools for .NET for a while —&nbsp;<a href="https://github.com/elbruno/elbruno.localembeddings">local embeddings</a>,&nbsp;<a href="https://elbruno.com/2026/02/23/%f0%9f%a4%96%f0%9f%97%a3%ef%b8%8f-local-ai-voices-in-net-vibevoice-qwen-tts/">local TTS with VibeVoice and QwenTTS</a>, and more. But what was missing was the&nbsp;<strong>glue</strong>: a framework that chains VAD → STT → LLM → TTS into a single, pluggable pipeline.</p>



<p class="wp-block-paragraph">I wanted something that:</p>



<ul class="wp-block-list">
<li>Follows&nbsp;<strong><a href="https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai">Microsoft.Extensions.AI</a></strong>&nbsp;patterns (no proprietary abstractions)</li>



<li>Uses&nbsp;<strong>Dependency Injection</strong>&nbsp;like any modern .NET app</li>



<li>Lets you&nbsp;<strong>swap any component</strong>&nbsp;— Whisper for STT, Kokoro or QwenTTS for TTS, Foundry Local or Ollama for chat</li>



<li><strong>Auto-downloads models</strong>&nbsp;on first run — no manual setup</li>



<li>Supports both&nbsp;<strong>one-shot</strong>&nbsp;and&nbsp;<strong>real-time streaming</strong>&nbsp;conversations</li>
</ul>



<p class="wp-block-paragraph">So I built it. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 class="wp-block-heading">The Architecture</h2>



<p class="wp-block-paragraph">ElBruno.Realtime uses a three-layer architecture:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">Your App</div><div class="cm-line">   ↓</div><div class="cm-line">┌─────────────────────────────────────┐</div><div class="cm-line">│   RealtimeConversationPipeline      │  ← Orchestration Layer</div><div class="cm-line">│   (Chains VAD → STT → LLM → TTS)    │</div><div class="cm-line">└─────────────────────────────────────┘</div><div class="cm-line">   ↓         ↓         ↓         ↓</div><div class="cm-line"> Silero    Whisper   Ollama    Kokoro/Qwen/VibeVoice</div><div class="cm-line">  VAD       STT      Chat       TTS</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Every component implements a standard interface —&nbsp;<code>ISpeechToTextClient</code>&nbsp;(from M.E.AI),&nbsp;<code>ITextToSpeechClient</code>,&nbsp;<code>IVoiceActivityDetector</code>,&nbsp;<code>IChatClient</code>&nbsp;— so they&#8217;re independently replaceable.</p>



<p class="wp-block-paragraph">Two processing modes:</p>



<ul class="wp-block-list">
<li><strong><code>ProcessTurnAsync</code></strong>&nbsp;— One-shot: give it a WAV file, get back transcription + AI response + audio</li>



<li><strong><code>ConverseAsync</code></strong>&nbsp;— Streaming: pipe live microphone audio, get real-time events as&nbsp;<code>IAsyncEnumerable&lt;ConversationEvent&gt;</code></li>
</ul>



<h2 class="wp-block-heading">NuGet Packages</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Package</th><th class="has-text-align-left" data-align="left">What it does</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><code>ElBruno.Realtime</code></td><td class="has-text-align-left" data-align="left">Core pipeline + abstractions</td></tr><tr><td class="has-text-align-left" data-align="left"><code>ElBruno.Realtime.Whisper</code></td><td class="has-text-align-left" data-align="left">Whisper.net STT (GGML models)</td></tr><tr><td class="has-text-align-left" data-align="left"><code>ElBruno.Realtime.SileroVad</code></td><td class="has-text-align-left" data-align="left">Silero VAD via ONNX Runtime</td></tr><tr><td class="has-text-align-left" data-align="left"><code>ElBruno.KokoroTTS.Realtime</code></td><td class="has-text-align-left" data-align="left">Kokoro-82M TTS (~320 MB, fast)</td></tr><tr><td class="has-text-align-left" data-align="left"><code>ElBruno.QwenTTS.Realtime</code></td><td class="has-text-align-left" data-align="left">QwenTTS (~5.5 GB, high quality)</td></tr><tr><td class="has-text-align-left" data-align="left"><code>ElBruno.VibeVoiceTTS.Realtime</code></td><td class="has-text-align-left" data-align="left">VibeVoice TTS (~1.5 GB)</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">All models auto-download on first use. No manual steps. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 class="wp-block-heading">Show Me the Code</h2>



<h3 class="wp-block-heading">Minimal Console App — One-Shot Conversation</h3>



<p class="wp-block-paragraph">This is the simplest possible setup. Record a question, get an AI response with audio:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">DependencyInjection</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">AI</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">services</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ServiceCollection</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Wire up the pipeline</span></div><div class="cm-line"><span class="tok-variableName">services</span>.<span class="tok-variableName">AddPersonaPlexRealtime</span>(<span class="tok-variableName">opts</span> <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">opts</span>.<span class="tok-variableName">DefaultSystemPrompt</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;You are a helpful assistant. Keep responses brief.&quot;</span>;</div><div class="cm-line">    <span class="tok-variableName">opts</span>.<span class="tok-variableName">DefaultLanguage</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;en-US&quot;</span>;</div><div class="cm-line">})</div><div class="cm-line">.<span class="tok-variableName">UseWhisperStt</span>(<span class="tok-string">&quot;whisper-tiny.en&quot;</span>)   <span class="tok-comment">// 75 MB model, auto-downloaded</span></div><div class="cm-line">.<span class="tok-variableName">UseSileroVad</span>()                      <span class="tok-comment">// ~2 MB model</span></div><div class="cm-line">.<span class="tok-variableName">UseKokoroTts</span>();                     <span class="tok-comment">// ~320 MB model</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Add any IChatClient — here we use Ollama</span></div><div class="cm-line"><span class="tok-variableName">services</span>.<span class="tok-variableName">AddChatClient</span>(</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">OllamaChatClient</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">Uri</span>(<span class="tok-string">&quot;http://localhost:11434&quot;</span>), <span class="tok-string">&quot;phi4-mini&quot;</span>));</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">provider</span> <span class="tok-operator">=</span> <span class="tok-variableName">services</span>.<span class="tok-variableName">BuildServiceProvider</span>();</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">conversation</span> <span class="tok-operator">=</span> <span class="tok-variableName">provider</span>.<span class="tok-variableName">GetRequiredService</span><span class="tok-operator">&lt;</span><span class="tok-variableName">IRealtimeConversationClient</span><span class="tok-operator">&gt;</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Process a WAV file</span></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">audio</span> <span class="tok-operator">=</span> <span class="tok-variableName">File</span>.<span class="tok-variableName">OpenRead</span>(<span class="tok-string">&quot;question.wav&quot;</span>);</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">turn</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">conversation</span>.<span class="tok-variableName">ProcessTurnAsync</span>(<span class="tok-variableName">audio</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;&#x1f4dd; You said: {turn.UserText}&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;&#x1f916; AI replied: {turn.ResponseText}&quot;</span>);</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;&#x23f1; Processing time: {turn.ProcessingTime.TotalMilliseconds:F0}ms&quot;</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it. First run downloads models automatically. After that, everything runs locally.</p>



<h3 class="wp-block-heading">Real-Time Streaming — Live Microphone</h3>



<p class="wp-block-paragraph">For real-time conversations,&nbsp;<code>ConverseAsync</code>&nbsp;gives you an&nbsp;<code>IAsyncEnumerable&lt;ConversationEvent&gt;</code>&nbsp;that streams events as they happen:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">evt</span> <span class="tok-keyword">in</span> <span class="tok-variableName">conversation</span>.<span class="tok-variableName">ConverseAsync</span>(</div><div class="cm-line">    <span class="tok-variableName">microphoneAudioStream</span>,</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">ConversationOptions</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">SystemPrompt</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;You are a friendly voice assistant.&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">SessionId</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;user-123&quot;</span>,      <span class="tok-comment">// Per-user conversation history</span></div><div class="cm-line">        <span class="tok-variableName">EnableBargeIn</span> <span class="tok-operator">=</span> <span class="tok-atom">true</span>,         <span class="tok-comment">// Allow interrupting</span></div><div class="cm-line">        <span class="tok-variableName">MaxConversationHistory</span> <span class="tok-operator">=</span> <span class="tok-number">20</span>,</div><div class="cm-line">    }))</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">switch</span> (<span class="tok-variableName">evt</span>.<span class="tok-variableName">Kind</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">case</span> <span class="tok-variableName">ConversationEventKind</span>.<span class="tok-variableName">SpeechDetected</span>:</div><div class="cm-line">            <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-string">&quot;&#x1f3a4; Speech detected...&quot;</span>);</div><div class="cm-line">            <span class="tok-keyword">break</span>;</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">case</span> <span class="tok-variableName">ConversationEventKind</span>.<span class="tok-variableName">TranscriptionComplete</span>:</div><div class="cm-line">            <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;&#x1f4dd; You: {evt.TranscribedText}&quot;</span>);</div><div class="cm-line">            <span class="tok-keyword">break</span>;</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">case</span> <span class="tok-variableName">ConversationEventKind</span>.<span class="tok-variableName">ResponseTextChunk</span>:</div><div class="cm-line">            <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">evt</span>.<span class="tok-variableName">ResponseText</span>);  <span class="tok-comment">// Streams token by token</span></div><div class="cm-line">            <span class="tok-keyword">break</span>;</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">case</span> <span class="tok-variableName">ConversationEventKind</span>.<span class="tok-variableName">ResponseAudioChunk</span>:</div><div class="cm-line">            <span class="tok-comment">// Play audio chunk in real-time</span></div><div class="cm-line">            <span class="tok-variableName">audioPlayer</span>.<span class="tok-variableName">EnqueueChunk</span>(<span class="tok-variableName">evt</span>.<span class="tok-variableName">ResponseAudio</span>);</div><div class="cm-line">            <span class="tok-keyword">break</span>;</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">case</span> <span class="tok-variableName">ConversationEventKind</span>.<span class="tok-variableName">ResponseComplete</span>:</div><div class="cm-line">            <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-string">&quot;\n&#x2705; Response complete&quot;</span>);</div><div class="cm-line">            <span class="tok-keyword">break</span>;</div><div class="cm-line">    }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The pipeline handles everything:</p>



<ol class="wp-block-list">
<li><strong>Silero VAD</strong>&nbsp;detects when you start/stop speaking</li>



<li><strong>Whisper</strong>&nbsp;transcribes your speech</li>



<li><strong>Ollama</strong>&nbsp;generates a response (streamed)</li>



<li><strong>Kokoro/QwenTTS</strong>&nbsp;converts the response to audio (streamed)</li>
</ol>



<p class="wp-block-paragraph">All async. All streaming. All local.</p>



<h3 class="wp-block-heading">ASP.NET Core API + SignalR</h3>



<p class="wp-block-paragraph">Want to expose this as a web API? Here&#8217;s the setup:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">builder</span> <span class="tok-operator">=</span> <span class="tok-variableName">WebApplication</span>.<span class="tok-variableName">CreateBuilder</span>(<span class="tok-variableName">args</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">Services</span>.<span class="tok-variableName">AddPersonaPlexRealtime</span>(<span class="tok-variableName">opts</span> <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">opts</span>.<span class="tok-variableName">DefaultSystemPrompt</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;You are a helpful assistant.&quot;</span>;</div><div class="cm-line">})</div><div class="cm-line">.<span class="tok-variableName">UseWhisperStt</span>(<span class="tok-string">&quot;whisper-tiny.en&quot;</span>)</div><div class="cm-line">.<span class="tok-variableName">UseSileroVad</span>()</div><div class="cm-line">.<span class="tok-variableName">UseKokoroTts</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">Services</span>.<span class="tok-variableName">AddChatClient</span>(</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">OllamaChatClient</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">Uri</span>(<span class="tok-string">&quot;http://localhost:11434&quot;</span>), <span class="tok-string">&quot;phi4-mini&quot;</span>));</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">Services</span>.<span class="tok-variableName">AddSignalR</span>();</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">app</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">Build</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// REST endpoint for one-shot turns</span></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapPost</span>(<span class="tok-string">&quot;/api/conversation/turn&quot;</span>, <span class="tok-keyword">async</span> (</div><div class="cm-line">    <span class="tok-variableName">HttpRequest</span> <span class="tok-variableName">request</span>,</div><div class="cm-line">    <span class="tok-variableName">IRealtimeConversationClient</span> <span class="tok-variableName">conversation</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">form</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">request</span>.<span class="tok-variableName">ReadFormAsync</span>();</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">audioFile</span> <span class="tok-operator">=</span> <span class="tok-variableName">form</span>.<span class="tok-variableName">Files</span>[<span class="tok-string">&quot;audio&quot;</span>];</div><div class="cm-line">    <span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">audioStream</span> <span class="tok-operator">=</span> <span class="tok-variableName">audioFile</span><span class="tok-operator">!</span>.<span class="tok-variableName">OpenReadStream</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">turn</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">conversation</span>.<span class="tok-variableName">ProcessTurnAsync</span>(<span class="tok-variableName">audioStream</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-keyword">new</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">userText</span> <span class="tok-operator">=</span> <span class="tok-variableName">turn</span>.<span class="tok-variableName">UserText</span>,</div><div class="cm-line">        <span class="tok-variableName">responseText</span> <span class="tok-operator">=</span> <span class="tok-variableName">turn</span>.<span class="tok-variableName">ResponseText</span>,</div><div class="cm-line">        <span class="tok-variableName">processingTimeMs</span> <span class="tok-operator">=</span> <span class="tok-variableName">turn</span>.<span class="tok-variableName">ProcessingTime</span>.<span class="tok-variableName">TotalMilliseconds</span>,</div><div class="cm-line">    });</div><div class="cm-line">});</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">Run</span>();</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And a SignalR hub for real-time streaming:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">ConversationHub</span> : <span class="tok-variableName">Hub</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">IRealtimeConversationClient</span> <span class="tok-variableName">_conversation</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-variableName">ConversationHub</span>(<span class="tok-variableName">IRealtimeConversationClient</span> <span class="tok-variableName">conversation</span>)</div><div class="cm-line">        <span class="tok-operator">=&gt;</span> <span class="tok-variableName">_conversation</span> <span class="tok-operator">=</span> <span class="tok-variableName">conversation</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-variableName">IAsyncEnumerable</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ConversationEventDto</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">StreamConversation</span>(</div><div class="cm-line">        <span class="tok-variableName">IAsyncEnumerable</span><span class="tok-operator">&lt;</span><span class="tok-typeName">byte</span>[]<span class="tok-operator">&gt;</span> <span class="tok-variableName">audioChunks</span>,</div><div class="cm-line">        <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-variableName">systemPrompt</span> <span class="tok-operator">=</span> <span class="tok-atom">null</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">evt</span> <span class="tok-keyword">in</span> <span class="tok-variableName">_conversation</span>.<span class="tok-variableName">ConverseAsync</span>(</div><div class="cm-line">            <span class="tok-variableName">audioChunks</span>,</div><div class="cm-line">            <span class="tok-keyword">new</span> <span class="tok-variableName">ConversationOptions</span> { <span class="tok-variableName">SystemPrompt</span> <span class="tok-operator">=</span> <span class="tok-variableName">systemPrompt</span> }))</div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-keyword">yield</span> <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ConversationEventDto</span></div><div class="cm-line">            {</div><div class="cm-line">                <span class="tok-variableName">Kind</span> <span class="tok-operator">=</span> <span class="tok-variableName">evt</span>.<span class="tok-variableName">Kind</span>.<span class="tok-variableName">ToString</span>(),</div><div class="cm-line">                <span class="tok-variableName">TranscribedText</span> <span class="tok-operator">=</span> <span class="tok-variableName">evt</span>.<span class="tok-variableName">TranscribedText</span>,</div><div class="cm-line">                <span class="tok-variableName">ResponseText</span> <span class="tok-operator">=</span> <span class="tok-variableName">evt</span>.<span class="tok-variableName">ResponseText</span>,</div><div class="cm-line">                <span class="tok-variableName">Timestamp</span> <span class="tok-operator">=</span> <span class="tok-variableName">evt</span>.<span class="tok-variableName">Timestamp</span>,</div><div class="cm-line">            };</div><div class="cm-line">        }</div><div class="cm-line">    }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Swap TTS Engines in One Line</h2>



<p class="wp-block-paragraph">One of the things I love about this design — changing the TTS engine is literally one line:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Option 1: Kokoro — fast, ~320 MB</span></div><div class="cm-line">.<span class="tok-variableName">UseKokoroTts</span>(<span class="tok-variableName">defaultVoice</span>: <span class="tok-string">&quot;af_heart&quot;</span>)</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Option 2: QwenTTS — high quality, ~5.5 GB</span></div><div class="cm-line">.<span class="tok-variableName">UseQwenTts</span>()</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Option 3: VibeVoice — balanced, ~1.5 GB</span></div><div class="cm-line">.<span class="tok-variableName">UseVibeVoiceTts</span>(<span class="tok-variableName">defaultVoice</span>: <span class="tok-string">&quot;Carter&quot;</span>)</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Same goes for STT — switch from tiny to base model for better accuracy:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Fast (75 MB)</span></div><div class="cm-line">.<span class="tok-variableName">UseWhisperStt</span>(<span class="tok-string">&quot;whisper-tiny.en&quot;</span>)</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// More accurate (142 MB)</span></div><div class="cm-line">.<span class="tok-variableName">UseWhisperStt</span>(<span class="tok-string">&quot;whisper-base.en&quot;</span>)</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Models — All Auto-Downloaded</h2>



<p class="wp-block-paragraph">No manual model management. First run might take a moment to download, after that everything is cached locally:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Model</th><th class="has-text-align-left" data-align="left">Size</th><th class="has-text-align-left" data-align="left">Purpose</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left">Silero VAD v5</td><td class="has-text-align-left" data-align="left">~2 MB</td><td class="has-text-align-left" data-align="left">Detect when you&#8217;re speaking</td></tr><tr><td class="has-text-align-left" data-align="left">Whisper tiny.en</td><td class="has-text-align-left" data-align="left">~75 MB</td><td class="has-text-align-left" data-align="left">Fast speech-to-text</td></tr><tr><td class="has-text-align-left" data-align="left">Whisper base.en</td><td class="has-text-align-left" data-align="left">~142 MB</td><td class="has-text-align-left" data-align="left">Accurate speech-to-text</td></tr><tr><td class="has-text-align-left" data-align="left">Kokoro-82M</td><td class="has-text-align-left" data-align="left">~320 MB</td><td class="has-text-align-left" data-align="left">Fast text-to-speech</td></tr><tr><td class="has-text-align-left" data-align="left">VibeVoice</td><td class="has-text-align-left" data-align="left">~1.5 GB</td><td class="has-text-align-left" data-align="left">Balanced text-to-speech</td></tr><tr><td class="has-text-align-left" data-align="left">QwenTTS</td><td class="has-text-align-left" data-align="left">~5.5 GB</td><td class="has-text-align-left" data-align="left">High-quality text-to-speech</td></tr><tr><td class="has-text-align-left" data-align="left">Phi4-Mini (Ollama)</td><td class="has-text-align-left" data-align="left">~2.7 GB</td><td class="has-text-align-left" data-align="left">LLM chat (manual:&nbsp;<code>ollama pull phi4-mini</code>)</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Models are cached at&nbsp;<code>%LOCALAPPDATA%/ElBruno/Realtime/</code>.</p>



<h2 class="wp-block-heading">Per-User Sessions</h2>



<p class="wp-block-paragraph">The framework includes built-in conversation history with per-user session management:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">turn</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">conversation</span>.<span class="tok-variableName">ProcessTurnAsync</span>(</div><div class="cm-line">    <span class="tok-variableName">audioStream</span>,</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">ConversationOptions</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">SessionId</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;user-456&quot;</span>,              <span class="tok-comment">// Each user gets their own history</span></div><div class="cm-line">        <span class="tok-variableName">MaxConversationHistory</span> <span class="tok-operator">=</span> <span class="tok-number">50</span>,         <span class="tok-comment">// Sliding window</span></div><div class="cm-line">        <span class="tok-variableName">SystemPrompt</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;You remember context from our previous messages.&quot;</span>,</div><div class="cm-line">    });</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><code>InMemoryConversationSessionStore</code>&nbsp;is the default — or inject your own&nbsp;<code>IConversationSessionStore</code>&nbsp;for Redis, database, etc.</p>



<h2 class="wp-block-heading">What&#8217;s Next</h2>



<p class="wp-block-paragraph">I have a few things on my mind:</p>



<ul class="wp-block-list">
<li>More STT engines (faster-whisper, Azure Speech)</li>



<li>WebRTC transport for browser-to-server streaming</li>



<li>.NET Aspire integration sample (scenario-03 is already in progress!)</li>



<li>Performance benchmarks across TTS engines</li>



<li>Full support for Foundry Local</li>
</ul>



<h2 class="wp-block-heading">Resources</h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> GitHub repo:&nbsp;<a href="https://github.com/elbruno/ElBruno.Realtime">https://github.com/elbruno/ElBruno.Realtime</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NuGet:&nbsp;<a href="https://www.nuget.org/packages/ElBruno.Realtime">ElBruno.Realtime</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d6.png" alt="📖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Microsoft.Extensions.AI:&nbsp;<a href="https://learn.microsoft.com/en-us/dotnet/ai/microsoft-extensions-ai">Official docs</a></li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f399.png" alt="🎙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Related post:&nbsp;<a href="https://elbruno.com/2026/02/23/%f0%9f%a4%96%f0%9f%97%a3%ef%b8%8f-local-ai-voices-in-net-vibevoice-qwen-tts/">Local AI Voices in .NET — VibeVoice &amp; Qwen TTS</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/03/02/%f0%9f%8e%99%ef%b8%8f%f0%9f%a4%96-real-time-ai-conversations-in-net-local-stt-tts-vad-and-llm-no-cloud-required/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37266</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/03/gemini_small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/03/gemini_small.png">
			<media:title type="html">gemini_small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#127912; Text-to-Image in .NET — FLUX.2 Pro in the Foundry and Stable Diffusion on Your Machine</title>
		<link>https://elbruno.com/2026/02/26/%f0%9f%8e%a8-text-to-image-in-net-flux-2-pro-in-the-foundry-and-stable-diffusion-on-your-machine/</link>
					<comments>https://elbruno.com/2026/02/26/%f0%9f%8e%a8-text-to-image-in-net-flux-2-pro-in-the-foundry-and-stable-diffusion-on-your-machine/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 18:25:31 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Flux2]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[ONNX]]></category>
		<category><![CDATA[technology]]></category>
		<category><![CDATA[Text-To-Image]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37257</guid>

					<description><![CDATA[Hi 👋 These days Microsoft announced FLUX.2 Flex on Microsoft Foundry, I immediately thought: &#8220;I need to wrap this for .NET developers.&#8221; So I setup a SQUAD team and I did it. And then I thought: &#8220;Wait — I have a couple of Test-to-Image local pet projects, what if my SQUAD also help to polish and publish this? [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image"><a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/images/banner.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/ElBruno.Text2Image/raw/main/images/banner.png" alt="Banner generated with FLUX.2 Pro on Microsoft Foundry" /></a></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">These days Microsoft announced <a href="https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/meet-flux-2-flex-for-text%E2%80%91heavy-design-and-ui-prototyping-now-available-on-micro/4496041" target="_blank" rel="noreferrer noopener">FLUX.2 Flex on Microsoft Foundry</a>, I immediately thought: <em>&#8220;I need to wrap this for .NET developers.&#8221;</em></p>



<p class="wp-block-paragraph">So <a href="https://www.youtube.com/watch?v=lX1wtTgCXNs" target="_blank" rel="noreferrer noopener">I setup a SQUAD team</a> and I did it. And then I thought: <em>&#8220;Wait — I have a couple of Test-to-Image local pet projects, what if my SQUAD also help to polish and publish this? Same interface, and of course let&#8217;s make it Microsoft Extensions for AI compatible&#8221;</em></p>



<p class="wp-block-paragraph">So I did that too. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">The result is&nbsp;<strong>ElBruno.Text2Image</strong>&nbsp;— a .NET library that generates images from text prompts using either&nbsp;<strong>FLUX.2 Pro via Microsoft Foundry</strong>&nbsp;(cloud) or&nbsp;<strong>Stable Diffusion via ONNX Runtime</strong>&nbsp;(local). Same clean API surface. Your choice of backend.</p>



<p class="wp-block-paragraph">Let me show you how it works.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2601.png" alt="☁" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Microsoft Foundry — FLUX.2 Pro (Flux.2 plex coming soon!)</h2>



<p class="wp-block-paragraph">FLUX.2 Pro from Black Forest Labs delivers photorealistic, cinematic-quality image generation. It runs on Microsoft Foundry infrastructure — no local GPU needed, no model downloads, just an API key and go.</p>



<p class="wp-block-paragraph">Here&#8217;s all you need:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.Text2Image;
using ElBruno.Text2Image.Foundry;

using var generator = new Flux2Generator(
    endpoint: "https://your-resource.services.ai.azure.com",
    apiKey: "your-api-key",
    modelId: "FLUX.2-pro");

var result = await generator.GenerateAsync(
    "a simple flat icon of a paintbrush and a sparkle, purple and blue gradient, white background");

await result.SaveAsync("flux2-output.png");
Console.WriteLine($"Generated in {result.InferenceTimeMs}ms");
</pre></div>


<p class="wp-block-paragraph">That logo for the NuGet packages? <strong>Generated with FLUX.2 Pro using this exact library.</strong>  Eating our own dog food here. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f436.png" alt="🐶" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 class="wp-block-heading">Setting up credentials</h3>



<p class="wp-block-paragraph">The library reads from User Secrets, environment variables, or&nbsp;<code>appsettings.json</code>. For local development:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
cd src/samples/scenario-03-flux2-cloud
dotnet user-secrets set FLUX2_ENDPOINT "https://your-resource.services.ai.azure.com"
dotnet user-secrets set FLUX2_API_KEY "your-api-key-here"
dotnet user-secrets set FLUX2_MODEL_NAME "FLUX.2-pro"
dotnet user-secrets set FLUX2_MODEL_ID "FLUX.2-pro"
</pre></div>


<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Fun fact:</strong>&nbsp;FLUX.2 models use the BFL (Black Forest Labs) Native API, not the OpenAI-compatible endpoint. The library handles this automatically — just provide your&nbsp;<code>.services.ai.azure.com</code>&nbsp;base URL and it builds the correct API path.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5a5.png" alt="🖥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Local Side — Stable Diffusion with ONNX Runtime</h2>



<p class="wp-block-paragraph">If you have a GPU, or even a powerful CPU, you may want to experiment locally.</p>



<p class="wp-block-paragraph">The library supports four local Stable Diffusion variants — all running via ONNX Runtime:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Model</th><th>Package</th><th>Best for</th></tr></thead><tbody><tr><td>Stable Diffusion 1.5</td><td><code>ElBruno.Text2Image.Cpu</code></td><td>General-purpose, works everywhere</td></tr><tr><td>LCM Dreamshaper v7</td><td><code>ElBruno.Text2Image.Cpu</code></td><td>Fast generation (fewer steps needed)</td></tr><tr><td>SDXL Turbo</td><td><code>ElBruno.Text2Image.Cpu</code></td><td>Quick drafts in 1–4 steps</td></tr><tr><td>Stable Diffusion 2.1</td><td><code>ElBruno.Text2Image.Cpu</code></td><td>Higher resolution (768×768)</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Models are automatically downloaded from HuggingFace on first use (because I know that downloading and setup local models is a tricky one).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.Text2Image;
using ElBruno.Text2Image.Models;

using var generator = new StableDiffusion15();

// Model downloads automatically on first run
await generator.EnsureModelAvailableAsync();

var result = await generator.GenerateAsync(
    "a simple flat icon of a paintbrush and a sparkle, purple and blue gradient, white background",
    new ImageGenerationOptions
    {
        NumInferenceSteps = 15,
        Width = 512,
        Height = 512
    });

await result.SaveAsync("local-output.png");
</pre></div>


<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="490" src="https://elbruno.com/wp-content/uploads/2026/02/image.png?w=1024" alt="" class="wp-image-37263" srcset="https://elbruno.com/wp-content/uploads/2026/02/image.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/02/image.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/02/image.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/02/image.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/02/image.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/02/image.png 1888w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">GPU acceleration<a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/blog-post-text2image-dotnet.md#gpu-acceleration"></a></h3>



<p class="wp-block-paragraph">The library auto-detects your hardware and picks the best execution provider:</p>



<pre class="wp-block-preformatted"># CPU (default — works everywhere)
dotnet add package ElBruno.Text2Image.Cpu

# NVIDIA GPU (CUDA — significantly faster)
dotnet add package ElBruno.Text2Image.Cuda

# DirectML (AMD/Intel/NVIDIA on Windows)
dotnet add package ElBruno.Text2Image.DirectML</pre>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50c.png" alt="🔌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> MEAI &#8211; One Interface, Multiple Backends<a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/blog-post-text2image-dotnet.md#-one-interface-multiple-backends"></a></h2>



<p class="wp-block-paragraph">Every generator — cloud or local — implements the same&nbsp;<code>IImageGenerator</code>&nbsp;interface&nbsp;<strong>and</strong>&nbsp;<code>Microsoft.Extensions.AI.IImageGenerator</code>. This means you can swap backends without changing your application logic:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Cloud
IImageGenerator generator = new Flux2Generator(endpoint, apiKey, modelId: "FLUX.2-pro");

// Local
IImageGenerator generator = new StableDiffusion15();

// Same API for both
var result = await generator.GenerateAsync("a futuristic cityscape at sunset");
await result.SaveAsync("output.png");
</pre></div>


<p class="wp-block-paragraph">If you&#8217;re building with Dependency Injection, the library has extension methods for that too ( I did my best here, I think there is room for improvement ).</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NuGet Packages</h2>



<p class="wp-block-paragraph">Five packages, pick what you need:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Package</th><th>Description</th></tr></thead><tbody><tr><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image">ElBruno.Text2Image</a></td><td>Core library (managed ONNX, no native runtime)</td></tr><tr><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Foundry">ElBruno.Text2Image.Foundry</a></td><td>FLUX.2 cloud via Microsoft Foundry</td></tr><tr><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cpu">ElBruno.Text2Image.Cpu</a></td><td>Local models with CPU execution</td></tr><tr><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.Cuda">ElBruno.Text2Image.Cuda</a></td><td>Local models with NVIDIA GPU (CUDA)</td></tr><tr><td><a href="https://www.nuget.org/packages/ElBruno.Text2Image.DirectML">ElBruno.Text2Image.DirectML</a></td><td>Local models with DirectML (AMD/Intel/NVIDIA on Windows)</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">All packages target&nbsp;<strong>.NET 8.0</strong>&nbsp;and&nbsp;<strong>.NET 10.0</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f917.png" alt="🤗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ONNX Models on HuggingFace</h2>



<p class="wp-block-paragraph">I exported and published the ONNX models to HuggingFace so the library can auto-download them:</p>



<ul class="wp-block-list">
<li><a href="https://huggingface.co/elbruno/stable-diffusion-2-1-ONNX">elbruno/stable-diffusion-2-1-ONNX</a></li>



<li><a href="https://huggingface.co/elbruno/sdxl-turbo-ONNX">elbruno/sdxl-turbo-ONNX</a></li>
</ul>



<p class="wp-block-paragraph">The export process is documented in the repo if you want to convert your own models.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Why I Built This</h2>



<p class="wp-block-paragraph">My goal has always been simple:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Make AI easy and natural for .NET developers.</p>
</blockquote>



<p class="wp-block-paragraph">We&#8217;ve made great progress with <a href="https://github.com/elbruno/elbruno.localembeddings">local embeddings</a>, <a href="https://github.com/elbruno/ElBruno.VibeVoiceTTS">local TTS</a>, and agent frameworks. But when it came to <strong>image generation</strong>, the story was a little tricky for us.</p>



<p class="wp-block-paragraph">IMHO, generating images from text should be as simple as:</p>



<ol class="wp-block-list">
<li>Adding a NuGet package</li>



<li>Writing a few lines of C#</li>



<li>Running your app</li>
</ol>



<p class="wp-block-paragraph">That&#8217;s it. Whether you&#8217;re calling FLUX.2 Pro in the cloud or running Stable Diffusion locally — same experience, same simplicity.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Links</h2>



<ul class="wp-block-list">
<li><strong>Repository:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image" target="_blank" rel="noreferrer noopener">github.com/elbruno/ElBruno.Text2Image</a></li>



<li><strong>NuGet:</strong> <a href="https://www.nuget.org/packages/ElBruno.Text2Image" target="_blank" rel="noreferrer noopener">nuget.org/packages/ElBruno.Text2Image</a></li>



<li><strong>FLUX.2 Flex Announcement:</strong> <a href="https://techcommunity.microsoft.com/blog/azure-ai-foundry-blog/meet-flux-2-flex-for-text%E2%80%91heavy-design-and-ui-prototyping-now-available-on-micro/4496041" target="_blank" rel="noreferrer noopener">Meet FLUX.2 Flex on Microsoft Foundry</a></li>



<li><strong>Setup Guide:</strong> <a href="https://github.com/elbruno/ElBruno.Text2Image/blob/main/docs/flux2-setup-guide.md" target="_blank" rel="noreferrer noopener">FLUX.2 Setup Guide</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/26/%f0%9f%8e%a8-text-to-image-in-net-flux-2-pro-in-the-foundry-and-stable-diffusion-on-your-machine/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37257</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-26-2026-01_13_39-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-26-2026-01_13_39-pm.png">
			<media:title type="html">ChatGPT Image Feb 26, 2026, 01_13_39 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/ElBruno.Text2Image/raw/main/images/banner.png">
			<media:title type="html">Banner generated with FLUX.2 Pro on Microsoft Foundry</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/image.png?w=1024"/>
	</item>
		<item>
		<title>&#129302;&#128483;️ Local AI Voices in .NET — VibeVoice &amp; Qwen TTS</title>
		<link>https://elbruno.com/2026/02/23/%f0%9f%a4%96%f0%9f%97%a3%ef%b8%8f-local-ai-voices-in-net-vibevoice-qwen-tts/</link>
					<comments>https://elbruno.com/2026/02/23/%f0%9f%a4%96%f0%9f%97%a3%ef%b8%8f-local-ai-voices-in-net-vibevoice-qwen-tts/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 23 Feb 2026 16:30:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[Qwen3]]></category>
		<category><![CDATA[TTS]]></category>
		<category><![CDATA[VibeVoice]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37246</guid>

					<description><![CDATA[Hi! Let’s look at these 2 code snippets… what’s behind them? 🧠 Snippet 1 — VibeVoice (Native TTS in .NET) This generates a WAV file from text using the VibeVoice-Realtime-0.5B model, running locally via ONNX.The first time you run it, the model is automatically downloaded. No REST calls. No API keys. No cloud dependency. 🧠 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="682" src="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=1024" alt="" class="wp-image-37254" srcset="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=150 150w, https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=300 300w, https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=768 768w, https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=1440 1440w, https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png 1536w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Let’s look at these 2 code snippets… what’s behind them?</p>



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Snippet 1 — VibeVoice (Native TTS in .NET)</h2>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.VibeVoiceTTS;

using var tts = new VibeVoiceSynthesizer();
await tts.EnsureModelAvailableAsync(); // auto-download model if needed

float&#91;] audio = await tts.GenerateAudioAsync("Hello! Welcome to VibeVoiceTTS.", "Carter");
tts.SaveWav("output.wav", audio);
</pre></div>


<p class="wp-block-paragraph">This generates a WAV file from text using the VibeVoice-Realtime-0.5B model, running locally via ONNX.<br />The first time you run it, the model is automatically downloaded.</p>



<p class="wp-block-paragraph">No REST calls. No API keys. No cloud dependency.</p>



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Snippet 2 — QwenTTS (Local TTS + Voice Cloning Ready)</h2>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using ElBruno.QwenTTS.Pipeline;

// Models are downloaded automatically on first execution
using var pipeline = await TtsPipeline.CreateAsync("models");
await pipeline.SynthesizeAsync("Hello world!", "ryan", "hello.wav", "english");
</pre></div>


<p class="wp-block-paragraph">This example uses a <strong>Qwen3-TTS ONNX pipeline</strong> to generate speech locally, fully in C#.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Why I Built This</h2>



<p class="wp-block-paragraph">My goal has always been simple:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Make AI easy and natural for .NET developers.</p>
</blockquote>



<p class="wp-block-paragraph">We’ve made great progress in:</p>



<ul class="wp-block-list">
<li>Embeddings</li>



<li>Agents</li>



<li>RAG</li>



<li>Local models</li>



<li>AI orchestration</li>
</ul>



<p class="wp-block-paragraph">But when it came to <strong>Text-to-Speech</strong>, there was a gap.</p>



<p class="wp-block-paragraph">Most solutions required:</p>



<ul class="wp-block-list">
<li>Python</li>



<li>External services</li>



<li>Complex wrappers</li>



<li>Non-.NET idioms</li>
</ul>



<p class="wp-block-paragraph">I didn’t like that. IMHO, then <strong>TTS should feel like C# — not like glue code around another ecosystem.</strong> With these repositories I&#8217;ll give a try.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What Makes This Different?</h2>



<p class="wp-block-paragraph">Both libraries are built around a few core principles:</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 100% Local Execution</h3>



<p class="wp-block-paragraph">Models run on your machine (or your server).</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ONNX + .NET Runtime</h3>



<p class="wp-block-paragraph">No Python in production.</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Auto Model Management</h3>



<p class="wp-block-paragraph">Models download automatically the first time you use them.</p>



<h3 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Idiomatic C# APIs</h3>



<p class="wp-block-paragraph">Async/await. Disposable patterns. Clean abstractions.</p>



<p class="wp-block-paragraph">If you can use <code>HttpClient</code>, you can use these libraries.<br />If you understand <code>Task</code>, you can generate AI-powered speech.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">VibeVoice — Simple and Direct</h2>



<p class="wp-block-paragraph">Repository: <a href="https://github.com/elbruno/ElBruno.VibeVoiceTTS?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.VibeVoiceTTS</a></p>



<p class="wp-block-paragraph">NuGet: <a href="https://www.nuget.org/packages/ElBruno.VibeVoiceTTS">https://www.nuget.org/packages/ElBruno.VibeVoiceTTS</a></p>



<p class="wp-block-paragraph">VibeVoice is ideal if you want:</p>



<ul class="wp-block-list">
<li>Fast setup</li>



<li>Built-in voice presets</li>



<li>Clean WAV output</li>



<li>Minimal configuration</li>
</ul>



<p class="wp-block-paragraph">It uses the <strong>VibeVoice-Realtime-0.5B ONNX model</strong> and exposes a straightforward synthesizer API.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">QwenTTS — Flexible and Powerful</h2>



<p class="wp-block-paragraph">Repository: <a href="https://github.com/elbruno/ElBruno.QwenTTS?utm_source=chatgpt.com">https://github.com/elbruno/ElBruno.QwenTTS</a></p>



<p class="wp-block-paragraph">NuGet: <a href="https://www.nuget.org/packages/ElBruno.QwenTTS">https://www.nuget.org/packages/ElBruno.QwenTTS</a></p>



<p class="wp-block-paragraph">QwenTTS is built around <strong>Qwen3-TTS</strong>, exported to ONNX and integrated into a C# pipeline.</p>



<p class="wp-block-paragraph">It supports:</p>



<ul class="wp-block-list">
<li>Multiple speakers</li>



<li>Multi-language scenarios</li>



<li>More advanced synthesis control</li>



<li>Voice cloning capabilities (via dedicated pipeline)</li>
</ul>



<p class="wp-block-paragraph">This opens the door to:</p>



<ul class="wp-block-list">
<li>Custom AI assistants</li>



<li>Personalized voice experiences</li>



<li>Voice-enabled RAG systems</li>



<li>AI avatars</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Why Local TTS Matters</h2>



<p class="wp-block-paragraph">Running TTS locally gives you:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Privacy — no text leaves your machine</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4b0.png" alt="💰" class="wp-smiley" style="height: 1em; max-height: 1em;" /> No per-request costs</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Low latency</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9ea.png" alt="🧪" class="wp-smiley" style="height: 1em; max-height: 1em;" /> A safe playground for experimentation</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Full control over deployment</li>
</ul>



<p class="wp-block-paragraph">If you’re exploring:</p>



<ul class="wp-block-list">
<li>Local AI</li>



<li>Foundry Local</li>



<li>Offline AI scenarios</li>



<li>Edge deployments</li>
</ul>



<p class="wp-block-paragraph">These libraries are a practical starting point.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Bonus: Voice Cloning (Work in progress)</h2>



<p class="wp-block-paragraph">The QwenTTS repository includes support for voice cloning via a dedicated pipeline.</p>



<p class="wp-block-paragraph">This means you can:</p>



<ul class="wp-block-list">
<li>Generate speech in a reference voice</li>



<li>Personalize assistant experiences</li>



<li>Experiment with identity-driven AI systems</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Final Thoughts</h2>



<p class="wp-block-paragraph">For me, generating natural speech locally should be as simple as:</p>



<ul class="wp-block-list">
<li>Adding a NuGet package</li>



<li>Writing a few lines of C#</li>



<li>Running your app</li>
</ul>



<p class="wp-block-paragraph">That’s it.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/23/%f0%9f%a4%96%f0%9f%97%a3%ef%b8%8f-local-ai-voices-in-net-vibevoice-qwen-tts/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37246</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png">
			<media:title type="html">ChatGPT Image Feb 22, 2026, 09_19_57 PM</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/chatgpt-image-feb-22-2026-09_19_57-pm.png?w=1024"/>
	</item>
		<item>
		<title>Microsoft Agent Framework is Release Candidate! Let’s Go &#128293;&#129302;</title>
		<link>https://elbruno.com/2026/02/23/microsoft-agent-framework-is-release-candidate-lets-go-%f0%9f%94%a5%f0%9f%a4%96/</link>
					<comments>https://elbruno.com/2026/02/23/microsoft-agent-framework-is-release-candidate-lets-go-%f0%9f%94%a5%f0%9f%a4%96/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 23 Feb 2026 12:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37236</guid>

					<description><![CDATA[Hi! Big milestone these days: The Microsoft Agent Framework (MAF) just reached Release Candidate status 🎉 Official announcement here:👉 https://devblogs.microsoft.com/foundry/microsoft-agent-framework-reaches-release-candidate/ As someone who has been building apps, samples, demos, orchestration experiments and livestream content around MAF for months… this one feels GOOD. Let’s talk about this. 🤖 What is Microsoft Agent Framework? The Microsoft Agent [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" width="1024" height="572" src="https://elbruno.com/wp-content/uploads/2026/02/maf.jpg?w=1024" alt="" class="wp-image-37237" srcset="https://elbruno.com/wp-content/uploads/2026/02/maf.jpg?w=1024 1024w, https://elbruno.com/wp-content/uploads/2026/02/maf.jpg?w=150 150w, https://elbruno.com/wp-content/uploads/2026/02/maf.jpg?w=300 300w, https://elbruno.com/wp-content/uploads/2026/02/maf.jpg?w=768 768w, https://elbruno.com/wp-content/uploads/2026/02/maf.jpg 1280w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">Big milestone these days: <strong>The Microsoft Agent Framework (MAF) just reached Release Candidate status <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></p>



<p class="wp-block-paragraph">Official announcement here:<br /><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://devblogs.microsoft.com/foundry/microsoft-agent-framework-reaches-release-candidate/">https://devblogs.microsoft.com/foundry/microsoft-agent-framework-reaches-release-candidate/</a></p>



<p class="wp-block-paragraph">As someone who has been building apps, samples, demos, orchestration experiments and livestream content around MAF for months… this one feels GOOD.</p>



<p class="wp-block-paragraph">Let’s talk about this.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> What is Microsoft Agent Framework?</h2>



<p class="wp-block-paragraph">The <strong>Microsoft Agent Framework</strong> is a .NET-first (and Python) framework to:</p>



<ul class="wp-block-list">
<li>Build AI Agents</li>



<li>Orchestrate multi-agent systems</li>



<li>Connect tools, memory, skills</li>



<li>Integrate with Azure AI and local models</li>



<li>Control execution, planning, routing</li>
</ul>



<p class="wp-block-paragraph">Think:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Structured AI orchestration for real-world production systems.</p>
</blockquote>



<p class="wp-block-paragraph">Not “just chat”. Not “just prompts”. Not “just LLM calls”.</p>



<p class="wp-block-paragraph">This is <strong>agent architecture in C#</strong>. And that’s why I like it a lot <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f60e.png" alt="😎" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">Built by Microsoft. Designed for real applications. Native .NET developer experience.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> A Minimal C# Agent Example</h1>



<p class="wp-block-paragraph">Let’s start simple.</p>



<p class="wp-block-paragraph">A minimal agent setup:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using System;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;

AIAgent agent = new AzureOpenAIClient(
        new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
        new AzureCliCredential())
    .GetChatClient("gpt-5")
    .AsAIAgent(instructions: "You are a friendly assistant. Keep your answers brief.");

Console.WriteLine(await agent.RunAsync("What is the largest city in France?"));
</pre></div>


<pre class="wp-block-preformatted">That’s it: </pre>



<ol class="wp-block-list">
<li>Create the agent app</li>



<li>Register an agent</li>



<li>Provide instructions</li>



<li>Execute</li>
</ol>



<p class="wp-block-paragraph">Super easy.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f527.png" alt="🔧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Adding a Tool (Because Agents Need Superpowers)</h1>



<p class="wp-block-paragraph">Let’s give our agent a tool.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using System;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
    ?? throw new InvalidOperationException("Set AZURE_OPENAI_ENDPOINT");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5";

AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
    .GetChatClient(deploymentName)
    .AsAIAgent(instructions: "You are a helpful assistant.", tools: &#91;AIFunctionFactory.Create(GetWeather)]);
</pre></div>


<p class="wp-block-paragraph">Example tool:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using System.ComponentModel;

&#91;Description(&quot;Get the weather for a given location.&quot;)]
static string GetWeather(&#91;Description(&quot;The location to get the weather for.&quot;)] string location)
    =&gt; $&quot;The weather in {location} is cloudy with a high of 15°C.&quot;;
</pre></div>


<p class="wp-block-paragraph">Now your agent:</p>



<ul class="wp-block-list">
<li>Decides when to call tools</li>



<li>Uses structured tool invocation</li>



<li>Returns enriched results</li>
</ul>



<p class="wp-block-paragraph">This is <strong>controlled autonomy</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e9.png" alt="🧩" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Multi-Agent Orchestration</h1>



<p class="wp-block-paragraph">Now we move from “chatbot” to architecture.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using System;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;

var client = new AzureOpenAIClient(
        new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
        new AzureCliCredential())
    .GetChatClient("gpt-5");

var planner = client.AsAIAgent(
    instructions: "Break down the task into steps."
);

var executor = client.AsAIAgent(
    instructions: "Execute each step carefully."
);

Workflow workflow = AgentWorkflowBuilder.BuildSequential(planner, executor);

</pre></div>


<p class="wp-block-paragraph">Then orchestrate:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var result = await workflow.RunAsync(
    "Create a blog post about AI agents."
);

Console.WriteLine(result);
</pre></div>


<p class="wp-block-paragraph">Now you’re orchestrating.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9f1.png" alt="🧱" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Where MAF Fits in the Stack</h1>



<p class="wp-block-paragraph">MAF works beautifully with:</p>



<ul class="wp-block-list">
<li>Azure AI</li>



<li>Local models</li>



<li>Tool calling</li>



<li>Structured execution</li>



<li>Observability</li>



<li>Enterprise-grade patterns</li>
</ul>



<p class="wp-block-paragraph">It’s not a demo framework. It’s a <strong>foundation</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> All My Microsoft Agent Framework Content</h1>



<p class="wp-block-paragraph">Over the last months I’ve built a LOT around MAF.</p>



<p class="wp-block-paragraph">Here’s a curated list of my content:</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Blog Posts</h2>



<ul class="wp-block-list">
<li>Deep dives into agent orchestration</li>



<li>Tool integration patterns</li>



<li>Multi-agent execution samples</li>



<li>Performance comparisons</li>



<li>Real C# implementation breakdowns</li>
</ul>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> You can find all posts tagged with Agent Framework on my blog: <a href="https://elbruno.com">https://elbruno.com</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3ac.png" alt="🎬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> YouTube Videos</h2>



<p class="wp-block-paragraph">On my YouTube channel I’ve covered:</p>



<ul class="wp-block-list">
<li>Intro to Microsoft Agent Framework</li>



<li>Multi-agent demos in C#</li>



<li>Agent orchestration patterns</li>



<li>Live coding sessions</li>



<li>Performance experiments</li>



<li>Comparison with other orchestration approaches</li>
</ul>



<p class="wp-block-paragraph">Channel: <a><a href="https://youtube.com/elbruno" rel="nofollow">https://youtube.com/elbruno</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f534.png" alt="🔴" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Livestreams</h2>



<p class="wp-block-paragraph">I’ve done:</p>



<ul class="wp-block-list">
<li>.NET Channels</li>



<li>Microsoft Reactor sessions</li>



<li>Community livestream demos</li>



<li>GitHub repo walkthroughs</li>



<li>Live coding of multi-agent apps</li>
</ul>



<p class="wp-block-paragraph">All focused on:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Real .NET developer experience.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> My GitHub Samples</h1>



<p class="wp-block-paragraph">Some of the repos I built around MAF include:</p>



<ul class="wp-block-list">
<li>Multi-agent orchestration samples</li>



<li>Performance comparison experiments</li>



<li>Tool-based execution demos</li>



<li>Local AI integration experiments</li>



<li>Hybrid Azure + local agent setups</li>
</ul>



<p class="wp-block-paragraph">Check them here: <a><a href="https://github.com/elbruno" rel="nofollow">https://github.com/elbruno</a></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Why the Release Candidate Matters</h1>



<p class="wp-block-paragraph">Release Candidate means:</p>



<ul class="wp-block-list">
<li>API stability</li>



<li>Production readiness direction</li>



<li>Ecosystem alignment</li>



<li>Documentation maturity</li>



<li>Clear forward path</li>
</ul>



<p class="wp-block-paragraph">This is no longer experimental territory. This is “start building real stuff”. And as a .NET developer? This is AMAZING!</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Final Thoughts</h1>



<p class="wp-block-paragraph">I’ve been saying this for a while. AI apps are not just about LLM calls. They’re about:</p>



<ul class="wp-block-list">
<li>Control</li>



<li>Orchestration</li>



<li>Tools</li>



<li>Deterministic flow</li>



<li>Observability</li>
</ul>



<p class="wp-block-paragraph">Microsoft Agent Framework gives us that <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<p class="wp-block-paragraph">If you’ve been experimenting with MAF too, tell me what you’re building <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">And if you haven’t started yet…</p>



<p class="wp-block-paragraph">Now is the perfect time <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/23/microsoft-agent-framework-is-release-candidate-lets-go-%f0%9f%94%a5%f0%9f%a4%96/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37236</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/maf.jpg"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/maf.jpg">
			<media:title type="html">maf</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/maf.jpg?w=1024"/>
	</item>
		<item>
		<title>Vision Memory Agent: local image search with CLIP + Ollama + Microsoft Agent Framework</title>
		<link>https://elbruno.com/2026/02/20/37230/</link>
					<comments>https://elbruno.com/2026/02/20/37230/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Fri, 20 Feb 2026 16:55:57 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[Embeddings]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[MAF]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37230</guid>

					<description><![CDATA[Hi! This post walks through a tiny but powerful scenario:&#160;ingest images locally, search them with natural language, and let an agent decide when to call tools. Everything runs on your machine, and it&#8217;s an simple example of how to use Microsoft Agent Framework with local embeddings. We’ll use: Repo sample:&#160;https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/VisionMemoryAgentSample Why this scenario You give [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">This post walks through a tiny but powerful scenario:&nbsp;<strong>ingest images locally, search them with natural language, and let an agent decide when to call tools</strong>. Everything runs on your machine, and it&#8217;s an simple example of how to use Microsoft Agent Framework with local embeddings.</p>



<p class="wp-block-paragraph">We’ll use:</p>



<ul class="wp-block-list">
<li><strong>CLIP</strong>&nbsp;image embeddings (ONNX) for fast local vector search.</li>



<li><strong>Ollama</strong>&nbsp;as the local LLM runtime.</li>



<li><strong>Microsoft Agent Framework</strong>&nbsp;to wire tool-calling into a conversational agent.</li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Repo sample:&nbsp;<a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/VisionMemoryAgentSample" target="_blank" rel="noreferrer noopener">https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/VisionMemoryAgentSample</a></p>
</blockquote>



<h2 class="wp-block-heading">Why this scenario</h2>



<p class="wp-block-paragraph">You give the agent an image path (or a folder), the agent stores embeddings locally, and later you ask it things like:</p>



<ul class="wp-block-list">
<li>“Find images similar to a beautiful sky at dusk.”</li>



<li>“Ingest all images from a folder.”</li>
</ul>



<p class="wp-block-paragraph">No tags required. The embeddings do the work.</p>



<h2 class="wp-block-heading">Prerequisites</h2>



<ul class="wp-block-list">
<li>.NET 10 SDK:&nbsp;<a href="https://dotnet.microsoft.com/download">https://dotnet.microsoft.com/download</a></li>



<li>Ollama:&nbsp;<a href="https://ollama.com/">https://ollama.com/</a></li>



<li>Microsoft Agent Framework (Ollama provider):&nbsp;<a href="https://learn.microsoft.com/en-us/agent-framework/agents/providers/ollama?pivots=programming-language-csharp">https://learn.microsoft.com/en-us/agent-framework/agents/providers/ollama?pivots=programming-language-csharp</a></li>
</ul>



<h2 class="wp-block-heading">Step 1 — Run Ollama and pull a model</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">ollama serve</div><div class="cm-line">ollama pull llama3.2</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Step 2 — Download CLIP models locally</h2>



<p class="wp-block-paragraph">Follow the repo guide to download the required CLIP assets locally:</p>



<p class="wp-block-paragraph"><a href="https://github.com/elbruno/elbruno.localembeddings/blob/main/samples/README_IMAGES.md#1-setup-download-models">https://github.com/elbruno/elbruno.localembeddings/blob/main/samples/README_IMAGES.md#1-setup-download-models</a></p>



<h2 class="wp-block-heading">Step 3 — Run the sample</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># From repo root</div><div class="cm-line"> dotnet run --project samples/VisionMemoryAgentSample -- --model-dir ./scripts/clip-models</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">The agent wiring (core code)</h2>



<p class="wp-block-paragraph">The heart of the sample is&nbsp;<strong>a local agent with function tools</strong>. Here’s the key setup:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-variableName">IChatClient</span> <span class="tok-variableName">chatClient</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">OllamaChatClient</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">Uri</span>(<span class="tok-string">&quot;http://localhost:11434&quot;</span>), </div><div class="cm-line">                                              <span class="tok-variableName">modelId</span>: <span class="tok-variableName">ollamaModel</span>)</div><div class="cm-line">    .<span class="tok-variableName">AsBuilder</span>()</div><div class="cm-line">    .<span class="tok-variableName">UseFunctionInvocation</span>()</div><div class="cm-line">    .<span class="tok-variableName">Build</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">tools</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>[]</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">AIFunctionFactory</span>.<span class="tok-variableName">Create</span>(<span class="tok-variableName">IngestImage</span>),</div><div class="cm-line">    <span class="tok-variableName">AIFunctionFactory</span>.<span class="tok-variableName">Create</span>(<span class="tok-variableName">FindSimilarImages</span>),</div><div class="cm-line">    <span class="tok-variableName">AIFunctionFactory</span>.<span class="tok-variableName">Create</span>(<span class="tok-variableName">IngestImagesFromFolder</span>)</div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">AIAgent</span> <span class="tok-variableName">agent</span> <span class="tok-operator">=</span> <span class="tok-variableName">chatClient</span>.<span class="tok-variableName">AsAIAgent</span>(</div><div class="cm-line">    <span class="tok-variableName">name</span>: <span class="tok-string">&quot;VisionMemoryAgent&quot;</span>,</div><div class="cm-line">    <span class="tok-variableName">instructions</span>: <span class="tok-string">&quot;&quot;&quot;</span></div><div class="cm-line">        <span class="tok-variableName">You</span> <span class="tok-variableName">are</span> <span class="tok-variableName">a</span> <span class="tok-variableName">Vision</span> <span class="tok-variableName">Memory</span> <span class="tok-variableName">agent</span>. <span class="tok-variableName">You</span> <span class="tok-variableName">help</span> <span class="tok-variableName">users</span> <span class="tok-variableName">manage</span> <span class="tok-variableName">and</span> <span class="tok-variableName">search</span> <span class="tok-variableName">a</span> <span class="tok-variableName">local</span> <span class="tok-variableName">image</span> <span class="tok-variableName">collection</span>.</div><div class="cm-line">        <span class="tok-variableName">You</span> <span class="tok-variableName">have</span> <span class="tok-variableName">three</span> <span class="tok-variableName">tools</span>:</div><div class="cm-line">        <span class="tok-operator">-</span> <span class="tok-variableName">IngestImage</span>: <span class="tok-variableName">to</span> <span class="tok-keyword">add</span> <span class="tok-variableName">an</span> <span class="tok-variableName">image</span> <span class="tok-variableName">to</span> <span class="tok-variableName">the</span> <span class="tok-keyword">in</span><span class="tok-operator">-</span><span class="tok-variableName">memory</span> <span class="tok-variableName">store</span></div><div class="cm-line">        <span class="tok-operator">-</span> <span class="tok-variableName">IngestImagesFromFolder</span>: <span class="tok-variableName">to</span> <span class="tok-keyword">add</span> <span class="tok-variableName">all</span> <span class="tok-variableName">images</span> <span class="tok-keyword">from</span> <span class="tok-variableName">a</span> <span class="tok-variableName">folder</span> <span class="tok-variableName">to</span> <span class="tok-variableName">the</span> <span class="tok-keyword">in</span><span class="tok-operator">-</span><span class="tok-variableName">memory</span> <span class="tok-variableName">store</span></div><div class="cm-line">        <span class="tok-operator">-</span> <span class="tok-variableName">FindSimilarImages</span>: <span class="tok-variableName">to</span> <span class="tok-variableName">search</span> <span class="tok-variableName">stored</span> <span class="tok-variableName">images</span> <span class="tok-keyword">using</span> <span class="tok-variableName">natural</span> <span class="tok-variableName">language</span></div><div class="cm-line">        <span class="tok-variableName">Always</span> <span class="tok-variableName">use</span> <span class="tok-variableName">the</span> <span class="tok-variableName">tools</span> <span class="tok-variableName">when</span> <span class="tok-variableName">the</span> <span class="tok-variableName">user</span> <span class="tok-variableName">asks</span> <span class="tok-variableName">to</span> <span class="tok-variableName">ingest</span> <span class="tok-variableName">or</span> <span class="tok-variableName">search</span> <span class="tok-variableName">images</span>.</div><div class="cm-line">        <span class="tok-variableName">Report</span> <span class="tok-variableName">tool</span> <span class="tok-variableName">results</span> <span class="tok-variableName">clearly</span>.</div><div class="cm-line">        <span class="tok-string">&quot;&quot;&quot;,</span></div><div class="cm-line">    <span class="tok-variableName">tools</span>: [.. <span class="tok-variableName">tools</span>]);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">AgentSession</span> <span class="tok-variableName">session</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">agent</span>.<span class="tok-variableName">CreateSessionAsync</span>();</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Why&nbsp;<code>UseFunctionInvocation()</code>&nbsp;matters</h3>



<p class="wp-block-paragraph">If you skip&nbsp;<code>UseFunctionInvocation()</code>, the model will&nbsp;<strong>return raw JSON tool calls</strong>&nbsp;instead of actually executing your tools. This one line is the bridge from “tool request” to “tool execution.”</p>



<h2 class="wp-block-heading">Tool 1 — Ingest a single image</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line">[<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Ingest a local image file and store its CLIP embedding in memory. Returns confirmation.&quot;</span>)]</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName tok-definition">IngestImage</span>(</div><div class="cm-line">    [<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Absolute or relative path to the image file&quot;</span>)] <span class="tok-typeName">string</span> <span class="tok-variableName">path</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-operator">!</span><span class="tok-variableName">File</span>.<span class="tok-variableName">Exists</span>(<span class="tok-variableName">path</span>))</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;Error: file not found: {path}&quot;</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-typeName">float</span>[] <span class="tok-variableName">embedding</span> <span class="tok-operator">=</span> <span class="tok-variableName">imageEncoder</span>.<span class="tok-variableName">Encode</span>(<span class="tok-variableName">path</span>);</div><div class="cm-line">    <span class="tok-variableName">imageStore</span>.<span class="tok-variableName">Add</span>((<span class="tok-variableName">path</span>, <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>, <span class="tok-variableName">embedding</span>));</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;Ingested &apos;{Path.GetFileName(path)}&apos;. Store now has {imageStore.Count} image(s).&quot;</span>;</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Tool 2 — Ingest all images in a folder</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line">[<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Ingest all the images from a local folder and store its CLIP embedding in memory. Returns confirmation.&quot;</span>)]</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName tok-definition">IngestImagesFromFolder</span>(</div><div class="cm-line">    [<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Absolute or relative path to the folder&quot;</span>)] <span class="tok-typeName">string</span> <span class="tok-variableName">folderPath</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-operator">!</span><span class="tok-variableName">Directory</span>.<span class="tok-variableName">Exists</span>(<span class="tok-variableName">folderPath</span>))</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;Error: folder not found: {folderPath}&quot;</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-typeName">string</span>[] <span class="tok-variableName">extensions</span> <span class="tok-operator">=</span> [<span class="tok-string">&quot;.jpg&quot;</span>, <span class="tok-string">&quot;.jpeg&quot;</span>, <span class="tok-string">&quot;.png&quot;</span>, <span class="tok-string">&quot;.bmp&quot;</span>, <span class="tok-string">&quot;.gif&quot;</span>];</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">imageFiles</span> <span class="tok-operator">=</span> <span class="tok-variableName">Directory</span>.<span class="tok-variableName">GetFiles</span>(<span class="tok-variableName">folderPath</span>)</div><div class="cm-line">        .<span class="tok-variableName">Where</span>(<span class="tok-variableName">f</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">extensions</span>.<span class="tok-variableName">Contains</span>(<span class="tok-variableName">Path</span>.<span class="tok-variableName">GetExtension</span>(<span class="tok-variableName">f</span>).<span class="tok-variableName">ToLowerInvariant</span>()))</div><div class="cm-line">        .<span class="tok-variableName">ToList</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-variableName">imageFiles</span>.<span class="tok-variableName">Count</span> <span class="tok-operator">==</span> <span class="tok-number">0</span>)</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;No images found in folder: {folderPath}&quot;</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-typeName">int</span> <span class="tok-variableName">ingestedCount</span> <span class="tok-operator">=</span> <span class="tok-number">0</span>;</div><div class="cm-line">    <span class="tok-keyword">foreach</span> (<span class="tok-typeName">string</span> <span class="tok-variableName">imagePath</span> <span class="tok-keyword">in</span> <span class="tok-variableName">imageFiles</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-typeName">float</span>[] <span class="tok-variableName">embedding</span> <span class="tok-operator">=</span> <span class="tok-variableName">imageEncoder</span>.<span class="tok-variableName">Encode</span>(<span class="tok-variableName">imagePath</span>);</div><div class="cm-line">        <span class="tok-variableName">imageStore</span>.<span class="tok-variableName">Add</span>((<span class="tok-variableName">imagePath</span>, <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>, <span class="tok-variableName">embedding</span>));</div><div class="cm-line">        <span class="tok-variableName">ingestedCount</span><span class="tok-operator">++</span>;</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;Ingested {ingestedCount} image(s) from &apos;{folderPath}&apos;. Store now has {imageStore.Count} image(s).&quot;</span>;</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Tool 3 — Find similar images</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line">[<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Find images similar to a natural language query using CLIP embeddings. Returns top matches.&quot;</span>)]</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName tok-definition">FindSimilarImages</span>(</div><div class="cm-line">    [<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Natural language search query&quot;</span>)] <span class="tok-typeName">string</span> <span class="tok-variableName">query</span>,</div><div class="cm-line">    [<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Number of top results to return&quot;</span>)] <span class="tok-typeName">int</span> <span class="tok-variableName">topK</span> <span class="tok-operator">=</span> <span class="tok-number">3</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-variableName">imageStore</span>.<span class="tok-variableName">Count</span> <span class="tok-operator">==</span> <span class="tok-number">0</span>)</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-string">&quot;No images in store. Ingest some images first.&quot;</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-typeName">float</span>[] <span class="tok-variableName">queryEmbedding</span> <span class="tok-operator">=</span> <span class="tok-variableName">textEncoder</span>.<span class="tok-variableName">Encode</span>(<span class="tok-variableName">query</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">results</span> <span class="tok-operator">=</span> <span class="tok-variableName">imageStore</span></div><div class="cm-line">        .<span class="tok-variableName">Select</span>(<span class="tok-variableName">img</span> <span class="tok-operator">=&gt;</span> (<span class="tok-variableName">img</span>.<span class="tok-variableName">Path</span>, <span class="tok-variableName">img</span>.<span class="tok-variableName">Tags</span>, <span class="tok-variableName">Score</span>: <span class="tok-variableName">TensorPrimitives</span>.<span class="tok-variableName">CosineSimilarity</span>(</div><div class="cm-line">            <span class="tok-variableName">queryEmbedding</span>.<span class="tok-variableName">AsSpan</span>(), <span class="tok-variableName">img</span>.<span class="tok-variableName">Embedding</span>.<span class="tok-variableName">AsSpan</span>())))</div><div class="cm-line">        .<span class="tok-variableName">OrderByDescending</span>(<span class="tok-variableName">r</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">r</span>.<span class="tok-variableName">Score</span>)</div><div class="cm-line">        .<span class="tok-variableName">Take</span>(<span class="tok-variableName">topK</span>)</div><div class="cm-line">        .<span class="tok-variableName">ToList</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">lines</span> <span class="tok-operator">=</span> <span class="tok-variableName">results</span>.<span class="tok-variableName">Select</span>((<span class="tok-variableName">r</span>, <span class="tok-variableName">i</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">        <span class="tok-variableName">$</span><span class="tok-string">&quot;  {i + 1}. {Path.GetFileName(r.Path)} (score: {r.Score:F4})&quot;</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;Top {results.Count} result(s):\n{string.Join(&quot;</span><span class="tok-variableName">\n</span><span class="tok-string">&quot;, lines)}&quot;</span>;</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Demo: conversational flow</h2>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">&gt; Please ingest the image at ./samples/images/cat.jpg</div><div class="cm-line">Ingested &apos;cat.jpg&apos;. Store now has 1 image(s).</div><div class="cm-line"></div><div class="cm-line">&gt; Ingest images from folder ./samples/images</div><div class="cm-line">Ingested 2 image(s) from &apos;./samples/images&apos;. Store now has 3 image(s).</div><div class="cm-line"></div><div class="cm-line">&gt; Find images similar to &quot;a beautiful sky at dusk&quot;</div><div class="cm-line">Top 1 result(s):</div><div class="cm-line">  1. sunset.jpg (score: 0.2845)</div></code></pre>
		</div>
	</div>
</div>


<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">You now have a&nbsp;<strong>fully local vision memory agent</strong>:</p>



<ul class="wp-block-list">
<li>Embeddings from CLIP</li>



<li>LLM coordination from Ollama</li>



<li>Tool orchestration via Microsoft Agent Framework</li>
</ul>



<p class="wp-block-paragraph">The end result is a clean, fast, and privacy-friendly way to explore image retrieval without sending anything to the cloud.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">References</h2>



<ul class="wp-block-list">
<li>Microsoft Agent Framework — Ollama provider:&nbsp;<a href="https://learn.microsoft.com/en-us/agent-framework/agents/providers/ollama?pivots=programming-language-csharp" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/agent-framework/agents/providers/ollama?pivots=programming-language-csharp</a></li>



<li>Agent Framework providers overview:&nbsp;<a href="https://learn.microsoft.com/en-us/agent-framework/agents/providers/" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/en-us/agent-framework/agents/providers/</a></li>



<li>Ollama homepage:&nbsp;<a href="https://ollama.com/" target="_blank" rel="noreferrer noopener">https://ollama.com/</a></li>



<li>Ollama quickstart:&nbsp;<a href="https://docs.ollama.com/quickstart" target="_blank" rel="noreferrer noopener">https://docs.ollama.com/quickstart</a></li>



<li>.NET SDK download:&nbsp;<a href="https://dotnet.microsoft.com/download" target="_blank" rel="noreferrer noopener">https://dotnet.microsoft.com/download</a></li>



<li>Repository:&nbsp;<a href="https://github.com/elbruno/elbruno.localembeddings" target="_blank" rel="noreferrer noopener">https://github.com/elbruno/elbruno.localembeddings</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/20/37230/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37230</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#128444;️ Local Image Embeddings in .NET — CLIP + ONNX</title>
		<link>https://elbruno.com/2026/02/16/%f0%9f%96%bc%ef%b8%8f-local-image-embeddings-in-net-clip-onnx/</link>
					<comments>https://elbruno.com/2026/02/16/%f0%9f%96%bc%ef%b8%8f-local-image-embeddings-in-net-clip-onnx/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 16 Feb 2026 16:51:41 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Embeddings]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Image Search]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[rag]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37217</guid>

					<description><![CDATA[Hi 👋 If you’ve used&#160;ElBruno.LocalEmbeddings&#160;for&#160;text&#160;embeddings, you’re going to love the new&#160;image&#160;capabilities. I asked several friends about this, and they challenge me to give it a try, so here it is: ElBruno.LocalEmbeddings.ImageEmbeddings&#160;a library brings&#160;CLIP-based multimodal embeddings&#160;to .NET — fully local. It is powered by ONNX Runtime, and ready for image search and image RAG workflows. In [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">If you’ve used&nbsp;<code>ElBruno.LocalEmbeddings</code>&nbsp;for&nbsp;<strong>text</strong>&nbsp;embeddings, you’re going to love the new&nbsp;<strong>image</strong>&nbsp;capabilities. I asked several friends about this, and they challenge me to give it a try, so here it is: </p>



<p class="wp-block-paragraph"><strong><code>ElBruno.LocalEmbeddings.ImageEmbeddings</code>&nbsp;a library brings&nbsp;CLIP-based multimodal embeddings&nbsp;to .NET — fully local.</strong> </p>



<p class="wp-block-paragraph">It is powered by ONNX Runtime, and ready for image search and image RAG workflows. In this post, I’ll show you:</p>



<ul class="wp-block-list">
<li>How to download the required CLIP models </li>



<li>A tiny “hello image embeddings” sample in C#</li>



<li>The two image samples included in the repo:&nbsp;<strong>ImageRagSimple</strong>&nbsp;and&nbsp;<strong>ImageRagChat</strong></li>
</ul>



<p class="wp-block-paragraph">Here is the RAGChat using images as a source:</p>



<figure class="wp-block-image size-large"><img src="https://raw.githubusercontent.com/elbruno/elbruno.localembeddings/main/docs/images/LocalEmbeddingsImages_ragchat.gif" alt="" /></figure>



<p class="wp-block-paragraph">Let’s dive in! <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph"><em><strong>Note:</strong> Right now, the auto-download feature as part of the library is Work-In-Progress, as these models are big. I&#8217;m working on the .NET library that do this (<a href="https://github.com/elbruno/elbruno.localembeddings/blob/main/docs/plans/plan_260216_1430.md#phase-2-model-downloader-library-option-e" target="_blank" rel="noreferrer noopener">see roadmap</a>), but I think so far with the download scripts we are OK.</em></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Library: Image Embeddings (CLIP)</h2>



<p class="wp-block-paragraph">The image embedding library is built on top of OpenAI’s&nbsp;<strong>CLIP</strong>&nbsp;model (Contrastive Language–Image Pretraining). It uses two ONNX models:</p>



<ul class="wp-block-list">
<li><strong>Text encoder</strong>&nbsp;→ embeds natural language queries</li>



<li><strong>Vision encoder</strong>&nbsp;→ embeds images</li>
</ul>



<p class="wp-block-paragraph">Both embeddings live in the same vector space, which means&nbsp;<strong>text-to-image</strong>&nbsp;and&nbsp;<strong>image-to-image</strong>&nbsp;search works with simple cosine similarity.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"></p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Download the CLIP Models</h2>



<p class="wp-block-paragraph">CLIP requires four files:</p>



<ul class="wp-block-list">
<li><code>text_model.onnx</code></li>



<li><code>vision_model.onnx</code></li>



<li><code>vocab.json</code></li>



<li><code>merges.txt</code></li>
</ul>



<p class="wp-block-paragraph">We provide scripts that download the correct files from Hugging Face.</p>



<h3 class="wp-block-heading">Windows (PowerShell)</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">./scripts/download_clip_models.ps1</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Linux / macOS (Bash)</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">chmod +x scripts/download_clip_models.sh</div><div class="cm-line">./scripts/download_clip_models.sh</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">These scripts download the models to:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">./scripts/clip-models</div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Basic Usage — Minimal C# Example</h2>



<p class="wp-block-paragraph">Here’s the simplest possible flow using the new library:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>.<span class="tok-variableName">ImageEmbeddings</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName">modelDir</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;./scripts/clip-models&quot;</span>;</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName">imageDir</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;./samples/images&quot;</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName">textModelPath</span> <span class="tok-operator">=</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(<span class="tok-variableName">modelDir</span>, <span class="tok-string">&quot;text_model.onnx&quot;</span>);</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName">visionModelPath</span> <span class="tok-operator">=</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(<span class="tok-variableName">modelDir</span>, <span class="tok-string">&quot;vision_model.onnx&quot;</span>);</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName">vocabPath</span> <span class="tok-operator">=</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(<span class="tok-variableName">modelDir</span>, <span class="tok-string">&quot;vocab.json&quot;</span>);</div><div class="cm-line"><span class="tok-typeName">string</span> <span class="tok-variableName">mergesPath</span> <span class="tok-operator">=</span> <span class="tok-variableName">Path</span>.<span class="tok-variableName">Combine</span>(<span class="tok-variableName">modelDir</span>, <span class="tok-string">&quot;merges.txt&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">textEncoder</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ClipTextEncoder</span>(<span class="tok-variableName">textModelPath</span>, <span class="tok-variableName">vocabPath</span>, <span class="tok-variableName">mergesPath</span>);</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">imageEncoder</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ClipImageEncoder</span>(<span class="tok-variableName">visionModelPath</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">searchEngine</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ImageSearchEngine</span>(<span class="tok-variableName">imageEncoder</span>, <span class="tok-variableName">textEncoder</span>);</div><div class="cm-line"><span class="tok-variableName">searchEngine</span>.<span class="tok-variableName">IndexImages</span>(<span class="tok-variableName">imageDir</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">results</span> <span class="tok-operator">=</span> <span class="tok-variableName">searchEngine</span>.<span class="tok-variableName">SearchByText</span>(<span class="tok-string">&quot;a cat&quot;</span>, <span class="tok-variableName">topK</span>: <span class="tok-number">3</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> (<span class="tok-variableName">imagePath</span>, <span class="tok-variableName">score</span>) <span class="tok-keyword">in</span> <span class="tok-variableName">results</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;{Path.GetFileName(imagePath)} → {score:F4}&quot;</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That’s it: index images → run text query → get ranked results.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9ea.png" alt="🧪" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sample 1: ImageRagSimple</h2>



<p class="wp-block-paragraph"><strong>ImageRagSimple</strong>&nbsp;is the most minimal sample. It demonstrates the core flow:</p>



<ol class="wp-block-list">
<li>Load CLIP text + vision models</li>



<li>Index all images in a folder</li>



<li>Run a few hardcoded text queries</li>
</ol>



<p class="wp-block-paragraph">This is the best sample to read if you want to understand the&nbsp;<strong>library usage</strong>&nbsp;with minimal noise.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4ac.png" alt="💬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sample 2: ImageRagChat</h2>



<p class="wp-block-paragraph"><strong>ImageRagChat</strong>&nbsp;builds on the same engine but adds a polished CLI experience using Spectre.Console. It supports:</p>



<ul class="wp-block-list">
<li>Live&nbsp;<strong>text-to-image search</strong></li>



<li><strong>Image-to-image search</strong>&nbsp;with&nbsp;<code>image:&lt;path&gt;</code></li>



<li>A readable, interactive UI</li>
</ul>



<p class="wp-block-paragraph">Commands inside the app:</p>



<ul class="wp-block-list">
<li>Type any text → search images</li>



<li>Type&nbsp;<code>image: path/to/image.jpg</code>&nbsp;→ image-to-image search</li>



<li>Type&nbsp;<code>exit</code>&nbsp;→ quit</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9ed.png" alt="🧭" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Which Sample Should You Start With?</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Sample</th><th class="has-text-align-left" data-align="left">Best For</th><th class="has-text-align-left" data-align="left">Notes</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><strong>ImageRagSimple</strong></td><td class="has-text-align-left" data-align="left">Learning the library API</td><td class="has-text-align-left" data-align="left">Straight-line demo, no UI</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>ImageRagChat</strong></td><td class="has-text-align-left" data-align="left">Interactive exploration</td><td class="has-text-align-left" data-align="left">(Better) UX + Chat mode</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3ac.png" alt="🎬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Video Walkthrough (Coming Soon)</h2>



<p class="wp-block-paragraph">Recorded a short video demo that walks through the library and both samples! </p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/nVTropZJC88?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Resources</h2>



<ul class="wp-block-list">
<li><a href="https://github.com/elbruno/elbruno.localembeddings/blob/main/samples/README_IMAGES.md" target="_blank" rel="noreferrer noopener">Image Embeddings setup guide</a></li>



<li><a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/ImageRagSimple" target="_blank" rel="noreferrer noopener">ImageRagSimple sample</a></li>



<li><a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/ImageRagChat" target="_blank" rel="noreferrer noopener">ImageRagChat sample</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/16/%f0%9f%96%bc%ef%b8%8f-local-image-embeddings-in-net-clip-onnx/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37217</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://raw.githubusercontent.com/elbruno/elbruno.localembeddings/main/docs/images/LocalEmbeddingsImages_ragchat.gif"/>
	</item>
		<item>
		<title>&#129504; Building RAG in .NET with Local Embeddings — 3 Approaches, Zero Cloud Calls</title>
		<link>https://elbruno.com/2026/02/14/%f0%9f%a7%a0-building-rag-in-net-with-local-embeddings-3-approaches-zero-cloud-calls/</link>
					<comments>https://elbruno.com/2026/02/14/%f0%9f%a7%a0-building-rag-in-net-with-local-embeddings-3-approaches-zero-cloud-calls/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Sun, 15 Feb 2026 01:00:13 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[Foundry Local]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Ollama]]></category>
		<category><![CDATA[rag]]></category>
		<category><![CDATA[technology]]></category>
		<category><![CDATA[Vector]]></category>
		<category><![CDATA[Vector Search]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37208</guid>

					<description><![CDATA[Hi! 👋 One of the questions I get most often is:&#160;&#8220;Bruno, can I build a RAG (Retrieval-Augmented Generation) app in .NET without sending my data to the cloud?&#8221; The answer is a resounding&#160;YES. 🚀 In this post, I&#8217;ll walk you through three different ways to build RAG applications using ElBruno.LocalEmbeddings — a .NET library that generates text embeddings locally using ONNX [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi! <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">One of the questions I get most often is:&nbsp;<strong>&#8220;Bruno, can I build a RAG (Retrieval-Augmented Generation) app in .NET without sending my data to the cloud?&#8221;</strong></p>



<p class="wp-block-paragraph">The answer is a resounding&nbsp;<strong>YES</strong>. <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">In this post, I&#8217;ll walk you through <strong>three different ways</strong> to build RAG applications using <a href="https://github.com/elbruno/elbruno.localembeddings" target="_blank" rel="noreferrer noopener">ElBruno.LocalEmbeddings</a> — a .NET library that generates text embeddings <strong>locally</strong> using ONNX Runtime. No external API calls for embeddings. Everything runs on your machine.</p>



<p class="wp-block-paragraph">Each approach uses a different level of abstraction:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">#</th><th class="has-text-align-left" data-align="left">Sample</th><th class="has-text-align-left" data-align="left">Pattern</th><th class="has-text-align-left" data-align="left">LLM</th><th class="has-text-align-left" data-align="left">Complexity</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left">1</td><td class="has-text-align-left" data-align="left"><strong>RagChat</strong></td><td class="has-text-align-left" data-align="left">Retrieval-only (no LLM)</td><td class="has-text-align-left" data-align="left">None</td><td class="has-text-align-left" data-align="left">VectorData + DI</td></tr><tr><td class="has-text-align-left" data-align="left">2</td><td class="has-text-align-left" data-align="left"><strong>RagOllama</strong></td><td class="has-text-align-left" data-align="left">Turnkey RAG</td><td class="has-text-align-left" data-align="left">Ollama (phi4-mini)</td><td class="has-text-align-left" data-align="left">Kernel Memory orchestrates everything</td></tr><tr><td class="has-text-align-left" data-align="left">3</td><td class="has-text-align-left" data-align="left"><strong>RagFoundryLocal</strong></td><td class="has-text-align-left" data-align="left">Manual RAG pipeline</td><td class="has-text-align-left" data-align="left">Foundry Local (phi-4-mini)</td><td class="has-text-align-left" data-align="left">Full control, core library only</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Library: ElBruno.LocalEmbeddings</h2>



<p class="wp-block-paragraph">Before we start, here&#8217;s the quick setup. The core NuGet package:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet add package ElBruno.LocalEmbeddings</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">And the companion packages we&#8217;ll use across the samples:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># For Microsoft.Extensions.VectorData integration (Sample 1)</div><div class="cm-line">dotnet add package ElBruno.LocalEmbeddings.VectorData</div><div class="cm-line"></div><div class="cm-line"># For Microsoft Kernel Memory integration (Sample 2)</div><div class="cm-line">dotnet add package ElBruno.LocalEmbeddings.KernelMemory</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The library implements&nbsp;<code>IEmbeddingGenerator&lt;string, Embedding&lt;float&gt;&gt;</code>&nbsp;from&nbsp;<a href="https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/">Microsoft.Extensions.AI</a>, so it plugs into any .NET AI pipeline that uses that abstraction. It downloads and caches&nbsp;<a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">HuggingFace sentence-transformer models</a>&nbsp;automatically — no manual model management needed.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Default model:</strong>&nbsp;<code>sentence-transformers/all-MiniLM-L6-v2</code>&nbsp;— 384-dimensional embeddings, ~90 MB download, cached locally after first run.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sample 1: RagChat — Semantic Search with VectorData (No LLM!)</h2>



<p class="wp-block-paragraph"><strong>The idea:</strong>&nbsp;Embed a set of FAQ documents, store them in an in-memory vector store, and let the user search by typing natural language queries. The system returns the most relevant documents ranked by cosine similarity.&nbsp;<strong>No LLM is involved</strong>&nbsp;— this is pure embedding-based retrieval.</p>



<p class="wp-block-paragraph">This sample uses the&nbsp;<code>ElBruno.LocalEmbeddings.VectorData</code>&nbsp;companion package, which integrates with&nbsp;<a href="https://learn.microsoft.com/dotnet/api/microsoft.extensions.vectordata">Microsoft.Extensions.VectorData</a>&nbsp;abstractions and includes a built-in&nbsp;<code>InMemoryVectorStore</code>.</p>



<h3 class="wp-block-heading">Step 1: Define the Document Model</h3>



<p class="wp-block-paragraph">First, we define a&nbsp;<code>Document</code>&nbsp;class using VectorData attributes:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">VectorData</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">sealed</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">Document</span></div><div class="cm-line">{</div><div class="cm-line">    [<span class="tok-variableName">VectorStoreKey</span>]</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">required</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Id</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line"></div><div class="cm-line">    [<span class="tok-variableName">VectorStoreData</span>]</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">required</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Title</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line"></div><div class="cm-line">    [<span class="tok-variableName">VectorStoreData</span>]</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">required</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Content</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line"></div><div class="cm-line">    [<span class="tok-variableName">VectorStoreVector</span>(<span class="tok-number">384</span>, <span class="tok-variableName">DistanceFunction</span> <span class="tok-operator">=</span> <span class="tok-variableName">DistanceFunction</span>.<span class="tok-variableName">CosineSimilarity</span>)]</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-variableName">ReadOnlyMemory</span><span class="tok-operator">&lt;</span><span class="tok-typeName">float</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">Vector</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">set</span>; }</div><div class="cm-line"></div><div class="cm-line">    [<span class="tok-variableName">VectorStoreData</span>]</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-variableName">Category</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Notice the&nbsp;<code>[VectorStoreVector(384)]</code>&nbsp;attribute — that matches the 384 dimensions of the default MiniLM model. The&nbsp;<code>DistanceFunction.CosineSimilarity</code>&nbsp;tells the vector store how to rank results.</p>



<h3 class="wp-block-heading">Step 2: Wire Up DI and Load Documents</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>.<span class="tok-variableName">VectorData</span>.<span class="tok-variableName">Extensions</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">AI</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">DependencyInjection</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">VectorData</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Step 1: Configure DI</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">services</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ServiceCollection</span>();</div><div class="cm-line"><span class="tok-variableName">services</span>.<span class="tok-variableName">AddLocalEmbeddingsWithInMemoryVectorStore</span>(<span class="tok-variableName">options</span> <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">options</span>.<span class="tok-variableName">ModelName</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;sentence-transformers/all-MiniLM-L6-v2&quot;</span>;</div><div class="cm-line">    <span class="tok-variableName">options</span>.<span class="tok-variableName">MaxSequenceLength</span> <span class="tok-operator">=</span> <span class="tok-number">256</span>;</div><div class="cm-line">    <span class="tok-variableName">options</span>.<span class="tok-variableName">EnsureModelDownloaded</span> <span class="tok-operator">=</span> <span class="tok-atom">true</span>;</div><div class="cm-line">})</div><div class="cm-line">.<span class="tok-variableName">AddVectorStoreCollection</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span>, <span class="tok-variableName">Document</span><span class="tok-operator">&gt;</span>(<span class="tok-string">&quot;faq&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">serviceProvider</span> <span class="tok-operator">=</span> <span class="tok-variableName">services</span>.<span class="tok-variableName">BuildServiceProvider</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Step 2: Resolve embedding generator + vector collection</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">embeddingGenerator</span> <span class="tok-operator">=</span> <span class="tok-variableName">serviceProvider</span></div><div class="cm-line">    .<span class="tok-variableName">GetRequiredService</span><span class="tok-operator">&lt;</span><span class="tok-variableName">IEmbeddingGenerator</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span>, <span class="tok-variableName">Embedding</span><span class="tok-operator">&lt;</span><span class="tok-typeName">float</span><span class="tok-operator">&gt;&gt;&gt;</span>();</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">faqCollection</span> <span class="tok-operator">=</span> <span class="tok-variableName">serviceProvider</span></div><div class="cm-line">    .<span class="tok-variableName">GetRequiredService</span><span class="tok-operator">&lt;</span><span class="tok-variableName">VectorStoreCollection</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span>, <span class="tok-variableName">Document</span><span class="tok-operator">&gt;&gt;</span>();</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">One line —&nbsp;<code>AddLocalEmbeddingsWithInMemoryVectorStore()</code>&nbsp;— registers both the local embedding generator&nbsp;<strong>and</strong>&nbsp;the in-memory vector store. Then we add a typed collection called&nbsp;<code>"faq"</code>&nbsp;for our&nbsp;<code>Document</code>&nbsp;model.</p>



<h3 class="wp-block-heading">Step 3: Batch Embed and Upsert</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Step 3: Load FAQ documents, batch-embed, upsert into vector store</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">documents</span> <span class="tok-operator">=</span> <span class="tok-variableName">SampleData</span>.<span class="tok-variableName">GetFaqDocuments</span>(); <span class="tok-comment">// 20 FAQ docs</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">embeddings</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">embeddingGenerator</span></div><div class="cm-line">    .<span class="tok-variableName">GenerateAsync</span>(<span class="tok-variableName">documents</span>.<span class="tok-variableName">Select</span>(<span class="tok-variableName">d</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">d</span>.<span class="tok-variableName">Content</span>).<span class="tok-variableName">ToList</span>());</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">for</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">i</span> <span class="tok-operator">=</span> <span class="tok-number">0</span>; <span class="tok-variableName">i</span> <span class="tok-operator">&lt;</span> <span class="tok-variableName">documents</span>.<span class="tok-variableName">Count</span>; <span class="tok-variableName">i</span><span class="tok-operator">++</span>)</div><div class="cm-line">    <span class="tok-variableName">documents</span>[<span class="tok-variableName">i</span>].<span class="tok-variableName">Vector</span> <span class="tok-operator">=</span> <span class="tok-variableName">embeddings</span>[<span class="tok-variableName">i</span>].<span class="tok-variableName">Vector</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-variableName">faqCollection</span>.<span class="tok-variableName">UpsertAsync</span>(<span class="tok-variableName">documents</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">We batch-embed all 20 documents at once (efficient!), assign vectors, and upsert them into the vector store.</p>



<h3 class="wp-block-heading">Step 4: Search Loop</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">while</span> (<span class="tok-atom">true</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">input</span> <span class="tok-operator">=</span> <span class="tok-variableName">Console</span>.<span class="tok-variableName">ReadLine</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-comment">// Embed the user query</span></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">queryEmbedding</span> <span class="tok-operator">=</span> (<span class="tok-keyword">await</span> <span class="tok-variableName">embeddingGenerator</span>.<span class="tok-variableName">GenerateAsync</span>([<span class="tok-variableName">input</span>]))[<span class="tok-number">0</span>];</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-comment">// Search the vector store</span></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">results</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">faqCollection</span></div><div class="cm-line">        .<span class="tok-variableName">SearchAsync</span>(<span class="tok-variableName">queryEmbedding</span>, <span class="tok-variableName">top</span>: <span class="tok-number">3</span>)</div><div class="cm-line">        .<span class="tok-variableName">ToListAsync</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-comment">// Filter by minimum similarity score</span></div><div class="cm-line">    <span class="tok-variableName">results</span> <span class="tok-operator">=</span> <span class="tok-variableName">results</span></div><div class="cm-line">        .<span class="tok-variableName">Where</span>(<span class="tok-variableName">r</span> <span class="tok-operator">=&gt;</span> (<span class="tok-variableName">r</span>.<span class="tok-variableName">Score</span> <span class="tok-operator">??</span> <span class="tok-number">0</span><span class="tok-variableName">d</span>) <span class="tok-operator">&gt;=</span> <span class="tok-number">0.2</span><span class="tok-variableName">d</span>)</div><div class="cm-line">        .<span class="tok-variableName">OrderByDescending</span>(<span class="tok-variableName">r</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">r</span>.<span class="tok-variableName">Score</span> <span class="tok-operator">??</span> <span class="tok-number">0</span><span class="tok-variableName">d</span>)</div><div class="cm-line">        .<span class="tok-variableName">ToList</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">result</span> <span class="tok-keyword">in</span> <span class="tok-variableName">results</span>)</div><div class="cm-line">        <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;  [{result.Score:P0}] {result.Record.Title}&quot;</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That&#8217;s it! The user types a question, we embed it, search the vector collection with&nbsp;<code>SearchAsync</code>, and display matches with their similarity scores. No LLM, no cloud calls, no API keys.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"></blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f999.png" alt="🦙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sample 2: RagOllama — Full RAG with Kernel Memory + Ollama</h2>



<p class="wp-block-paragraph"><strong>The idea:</strong> Use <a href="https://github.com/microsoft/kernel-memory" target="_blank" rel="noreferrer noopener">Microsoft Kernel Memory</a> to orchestrate the entire RAG pipeline — chunking, embedding, storage, retrieval, prompt building, and LLM response — with a single <code>.WithLocalEmbeddings()</code> call for the embedding part and <a href="https://ollama.com/" target="_blank" rel="noreferrer noopener">Ollama</a> running <code>phi4-mini</code> locally for text generation.</p>



<p class="wp-block-paragraph">This is the&nbsp;<strong>&#8220;turnkey&#8221; approach</strong>&nbsp;— Kernel Memory handles everything. You just import text and ask questions.</p>



<h3 class="wp-block-heading">The Before/After Pattern</h3>



<p class="wp-block-paragraph">This sample first asks the question&nbsp;<strong>without</strong>&nbsp;any memory (baseline), then asks the same question&nbsp;<strong>with</strong>&nbsp;RAG to show the difference:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>.<span class="tok-variableName">KernelMemory</span>.<span class="tok-variableName">Extensions</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">KernelMemory</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">KernelMemory</span>.<span class="tok-variableName">AI</span>.<span class="tok-variableName">Ollama</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">KernelMemory</span>.<span class="tok-variableName">Configuration</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">OllamaSharp</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">ollamaEndpoint</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;http://localhost:11434&quot;</span>;</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">modelIdChat</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;phi4-mini&quot;</span>;</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">question</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;What is Bruno&apos;s favourite super hero?&quot;</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// &#x274c; Ask WITHOUT memory — the model doesn&apos;t know the answer</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">ollama</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">OllamaApiClient</span>(<span class="tok-variableName">ollamaEndpoint</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">SelectedModel</span> <span class="tok-operator">=</span> <span class="tok-variableName">modelIdChat</span></div><div class="cm-line">};</div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-string">&quot;Answer WITHOUT memory:&quot;</span>);</div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">token</span> <span class="tok-keyword">in</span> <span class="tok-variableName">ollama</span>.<span class="tok-variableName">GenerateAsync</span>(<span class="tok-variableName">question</span>))</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">token</span>.<span class="tok-variableName">Response</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Without context, the LLM just guesses. Now let&#8217;s build the RAG pipeline:</p>



<h3 class="wp-block-heading">Build Kernel Memory with Local Embeddings</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Configure Ollama for text generation</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">config</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">OllamaConfig</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Endpoint</span> <span class="tok-operator">=</span> <span class="tok-variableName">ollamaEndpoint</span>,</div><div class="cm-line">    <span class="tok-variableName">TextModel</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">OllamaModelConfig</span>(<span class="tok-variableName">modelIdChat</span>)</div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Build Kernel Memory: Ollama for chat + local embeddings for vectors</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">memory</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">KernelMemoryBuilder</span>()</div><div class="cm-line">    .<span class="tok-variableName">WithOllamaTextGeneration</span>(<span class="tok-variableName">config</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithLocalEmbeddings</span>()  <span class="tok-comment">// &#x1f448; This is the magic line!</span></div><div class="cm-line">    .<span class="tok-variableName">WithCustomTextPartitioningOptions</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">TextPartitioningOptions</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">MaxTokensPerParagraph</span> <span class="tok-operator">=</span> <span class="tok-number">256</span>,</div><div class="cm-line">        <span class="tok-variableName">OverlappingTokens</span> <span class="tok-operator">=</span> <span class="tok-number">50</span></div><div class="cm-line">    })</div><div class="cm-line">    .<span class="tok-variableName">Build</span>();</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><code>.WithLocalEmbeddings()</code> is an extension method from the <code>ElBruno.LocalEmbeddings.KernelMemory</code> companion package. Under the hood, it creates a <code>LocalEmbeddingGenerator</code> with default options and wraps it in a <code>LocalEmbeddingTextGenerator</code> adapter that implements Kernel Memory&#8217; <code>ITextEmbeddingGenerator</code> interface. One line, zero configuration.</p>



<h3 class="wp-block-heading">Import Facts and Ask with Memory</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Import facts into memory</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">facts</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>[]</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-string">&quot;Gisela&apos;s favourite super hero is Batman&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Gisela watched Venom 3 2 weeks ago&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Bruno&apos;s favourite super hero is Invincible&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Bruno went to the cinema to watch Venom 3&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Bruno doesn&apos;t like the super hero movie: Eternals&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;ACE and Goku watched the movies Venom 3 and Eternals&quot;</span>,</div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">for</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">i</span> <span class="tok-operator">=</span> <span class="tok-number">0</span>; <span class="tok-variableName">i</span> <span class="tok-operator">&lt;</span> <span class="tok-variableName">facts</span>.<span class="tok-variableName">Length</span>; <span class="tok-variableName">i</span><span class="tok-operator">++</span>)</div><div class="cm-line">    <span class="tok-keyword">await</span> <span class="tok-variableName">memory</span>.<span class="tok-variableName">ImportTextAsync</span>(<span class="tok-variableName">facts</span>[<span class="tok-variableName">i</span>], (<span class="tok-variableName">i</span> <span class="tok-operator">+</span> <span class="tok-number">1</span>).<span class="tok-variableName">ToString</span>());</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// &#x2705; Ask WITH memory — now the model knows!</span></div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-string">&quot;\nAnswer WITH memory:&quot;</span>);</div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">result</span> <span class="tok-keyword">in</span> <span class="tok-variableName">memory</span>.<span class="tok-variableName">AskStreamingAsync</span>(<span class="tok-variableName">question</span>))</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">result</span>.<span class="tok-variableName">Result</span>);</div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-variableName">result</span>.<span class="tok-variableName">RelevantSources</span>.<span class="tok-variableName">Count</span> <span class="tok-operator">&gt;</span> <span class="tok-number">0</span>)</div><div class="cm-line">        <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">source</span> <span class="tok-keyword">in</span> <span class="tok-variableName">result</span>.<span class="tok-variableName">RelevantSources</span>)</div><div class="cm-line">            <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;  [source: #{source.Index}] {source.SourceUrl}&quot;</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">When you call&nbsp;<code>ImportTextAsync</code>, Kernel Memory automatically:</p>



<ol class="wp-block-list">
<li><strong>Chunks</strong> the text (256 tokens per paragraph, 50 overlapping)</li>



<li><strong>Embeds</strong> each chunk using our local ONNX model</li>



<li><strong>Stores</strong> the chunks and vectors in its built-in store</li>
</ol>



<p class="wp-block-paragraph">When you call&nbsp;<code>AskStreamingAsync</code>, it:</p>



<ol class="wp-block-list">
<li><strong>Embeds</strong> the question</li>



<li><strong>Retrieves</strong> the most relevant chunks</li>



<li><strong>Builds</strong> a prompt with the context</li>



<li><strong>Streams</strong> the LLM response from Ollama</li>
</ol>



<p class="wp-block-paragraph">All in one call. The answer now correctly says &#8220;Bruno&#8217;s favourite super hero is Invincible&#8221; — with source citations! <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"></blockquote>



<h3 class="wp-block-heading">Prerequisites</h3>



<ul class="wp-block-list">
<li><a href="https://ollama.com/" target="_blank" rel="noreferrer noopener">Ollama</a> running locally with <code>phi4-mini</code> pulled</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sample 3: RagFoundryLocal — Manual RAG with Foundry Local</h2>



<p class="wp-block-paragraph"><strong>The idea:</strong> Build the entire RAG pipeline <strong>by hand</strong> — embed facts, search with <code>FindClosest()</code>, construct a prompt template, and stream the LLM response. This sample uses only the core <code>ElBruno.LocalEmbeddings</code> package (no companion packages) and <a href="https://learn.microsoft.com/ai/foundry/foundry-local/get-started" target="_blank" rel="noreferrer noopener">Microsoft AI Foundry Local</a> for the LLM.</p>



<p class="wp-block-paragraph">This is the&nbsp;<strong>&#8220;full control&#8221; approach</strong>&nbsp;— every step is explicit.</p>



<h3 class="wp-block-heading">Start the Model and Ask Without Context</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>.<span class="tok-variableName">Extensions</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">AI</span>.<span class="tok-variableName">Foundry</span>.<span class="tok-variableName">Local</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Extensions</span>.<span class="tok-variableName">AI</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">OpenAI</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">System</span>.<span class="tok-variableName">ClientModel</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">modelAlias</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;phi-4-mini&quot;</span>;</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">question</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;What is Bruno&apos;s favourite super hero?&quot;</span>;</div><div class="cm-line"><span class="tok-keyword">const</span> <span class="tok-typeName">int</span> <span class="tok-variableName">topK</span> <span class="tok-operator">=</span> <span class="tok-number">3</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Start Foundry Local model</span></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">manager</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">FoundryLocalManager</span>.<span class="tok-variableName">StartModelAsync</span>(<span class="tok-variableName">modelAlias</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Resolve the alias to the actual model ID registered on the server</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">modelIdChat</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">ResolveModelIdAsync</span>(<span class="tok-variableName">manager</span>.<span class="tok-variableName">Endpoint</span>, <span class="tok-variableName">modelAlias</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">openAiClient</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">OpenAIClient</span>(</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">ApiKeyCredential</span>(<span class="tok-variableName">manager</span>.<span class="tok-variableName">ApiKey</span>),</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">OpenAIClientOptions</span> { <span class="tok-variableName">Endpoint</span> <span class="tok-operator">=</span> <span class="tok-variableName">manager</span>.<span class="tok-variableName">Endpoint</span> });</div><div class="cm-line"><span class="tok-variableName">IChatClient</span> <span class="tok-variableName">chatClient</span> <span class="tok-operator">=</span> <span class="tok-variableName">openAiClient</span></div><div class="cm-line">    .<span class="tok-variableName">GetChatClient</span>(<span class="tok-variableName">modelIdChat</span>)</div><div class="cm-line">    .<span class="tok-variableName">AsIChatClient</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// &#x274c; Ask without context (baseline)</span></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">update</span> <span class="tok-keyword">in</span> <span class="tok-variableName">chatClient</span>.<span class="tok-variableName">GetStreamingResponseAsync</span>(</div><div class="cm-line">    [<span class="tok-keyword">new</span> <span class="tok-variableName">ChatMessage</span>(<span class="tok-variableName">ChatRole</span>.<span class="tok-variableName">User</span>, <span class="tok-variableName">question</span>)]))</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">update</span>.<span class="tok-variableName">Text</span>);</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph"><a href="https://learn.microsoft.com/ai/foundry/foundry-local/get-started" target="_blank" rel="noreferrer noopener">Foundry Local</a> starts a local inference server and exposes an OpenAI-compatible API. We use <code>IChatClient</code> from Microsoft.Extensions.AI — the same abstraction you&#8217;d use with Azure OpenAI or any other provider.</p>



<h3 class="wp-block-heading">Build the RAG Pipeline Step by Step</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Same facts as the Ollama sample</span></div><div class="cm-line"><span class="tok-typeName">string</span>[] <span class="tok-variableName">facts</span> <span class="tok-operator">=</span></div><div class="cm-line">[</div><div class="cm-line">    <span class="tok-string">&quot;Gisela&apos;s favourite super hero is Batman&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Gisela watched Venom 3 2 weeks ago&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Bruno&apos;s favourite super hero is Invincible&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Bruno went to the cinema to watch Venom 3&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Bruno doesn&apos;t like the super hero movie: Eternals&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;ACE and Goku watched the movies Venom 3 and Eternals&quot;</span>,</div><div class="cm-line">];</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Step 1: Embed all facts locally</span></div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">embeddingGenerator</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">LocalEmbeddingGenerator</span>();</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">factEmbeddings</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">embeddingGenerator</span>.<span class="tok-variableName">GenerateAsync</span>(<span class="tok-variableName">facts</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Step 2: Zip facts with their embeddings</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">indexedFacts</span> <span class="tok-operator">=</span> <span class="tok-variableName">facts</span>.<span class="tok-variableName">Zip</span>(</div><div class="cm-line">    <span class="tok-variableName">factEmbeddings</span>,</div><div class="cm-line">    (<span class="tok-variableName">fact</span>, <span class="tok-variableName">embedding</span>) <span class="tok-operator">=&gt;</span> (<span class="tok-variableName">Item</span>: <span class="tok-variableName">fact</span>, <span class="tok-variableName">Embedding</span>: <span class="tok-variableName">embedding</span>));</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Step 3: Embed the question and find closest matches</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">queryEmbedding</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">embeddingGenerator</span>.<span class="tok-variableName">GenerateEmbeddingAsync</span>(<span class="tok-variableName">question</span>);</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">contextDocs</span> <span class="tok-operator">=</span> <span class="tok-variableName">indexedFacts</span></div><div class="cm-line">    .<span class="tok-variableName">FindClosest</span>(<span class="tok-variableName">queryEmbedding</span>, <span class="tok-variableName">topK</span>: <span class="tok-variableName">topK</span>)</div><div class="cm-line">    .<span class="tok-variableName">Select</span>(<span class="tok-variableName">match</span> <span class="tok-operator">=&gt;</span> <span class="tok-variableName">match</span>.<span class="tok-variableName">Item</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Here we use two key extension methods from the core library:</p>



<ul class="wp-block-list">
<li><strong><code>GenerateEmbeddingAsync(string)</code></strong> — convenience method that returns a single <code>Embedding&lt;float&gt;</code> directly (no array indexing needed)</li>



<li><strong><code>FindClosest()</code></strong> — extension on <code>IEnumerable&lt;(T Item, Embedding&lt;float&gt;)&gt;</code> that performs cosine similarity ranking and returns the top-K matches</li>
</ul>



<p class="wp-block-paragraph">No vector store, no DI container — just LINQ and extension methods.</p>



<h3 class="wp-block-heading">Build the Prompt and Stream the Response</h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Step 4: Build the prompt with retrieved context</span></div><div class="cm-line"><span class="tok-keyword">static</span> <span class="tok-typeName">string</span> <span class="tok-variableName tok-definition">BuildPrompt</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">question</span>, <span class="tok-variableName">IEnumerable</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">contextDocs</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">context</span> <span class="tok-operator">=</span> <span class="tok-typeName">string</span>.<span class="tok-variableName">Join</span>(<span class="tok-string">&quot;\n- &quot;</span>, <span class="tok-variableName">contextDocs</span>);</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;&quot;&quot;</span></div><div class="cm-line">        <span class="tok-variableName">You</span> <span class="tok-variableName">are</span> <span class="tok-variableName">a</span> <span class="tok-variableName">helpful</span> <span class="tok-variableName">assistant</span>. <span class="tok-variableName">Use</span> <span class="tok-variableName">the</span> <span class="tok-variableName">provided</span> <span class="tok-variableName">context</span></div><div class="cm-line">        <span class="tok-variableName">to</span> <span class="tok-variableName">answer</span> <span class="tok-variableName">briefly</span> <span class="tok-variableName">and</span> <span class="tok-variableName">accurately</span>.</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-variableName">Context</span>:</div><div class="cm-line">        <span class="tok-operator">-</span> {<span class="tok-variableName">context</span>}</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-variableName">Question</span>: {<span class="tok-variableName">question</span>}</div><div class="cm-line">        <span class="tok-string">&quot;&quot;&quot;;</span></div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Step 5: Ask the LLM with context &#x2705;</span></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">update</span> <span class="tok-keyword">in</span> <span class="tok-variableName">chatClient</span>.<span class="tok-variableName">GetStreamingResponseAsync</span>(</div><div class="cm-line">    [<span class="tok-keyword">new</span> <span class="tok-variableName">ChatMessage</span>(<span class="tok-variableName">ChatRole</span>.<span class="tok-variableName">User</span>, <span class="tok-variableName">BuildPrompt</span>(<span class="tok-variableName">question</span>, <span class="tok-variableName">contextDocs</span>))]))</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">Write</span>(<span class="tok-variableName">update</span>.<span class="tok-variableName">Text</span>);</div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">We build a simple prompt template using C# raw string literals, inject the retrieved context, and stream the response. The LLM now has the relevant facts and answers correctly.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"></blockquote>



<h3 class="wp-block-heading">Prerequisites</h3>



<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/ai/foundry/foundry-local/get-started" target="_blank" rel="noreferrer noopener">Foundry Local</a> installed with <code>phi-4-mini</code> available</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Comparison: Which Approach Should You Use?</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">Aspect</th><th class="has-text-align-left" data-align="left">RagChat</th><th class="has-text-align-left" data-align="left">RagOllama</th><th class="has-text-align-left" data-align="left">RagFoundryLocal</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left"><strong>LLM</strong></td><td class="has-text-align-left" data-align="left">None (retrieval only)</td><td class="has-text-align-left" data-align="left">Ollama phi4-mini</td><td class="has-text-align-left" data-align="left">Foundry Local phi-4-mini</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Embedding integration</strong></td><td class="has-text-align-left" data-align="left">DI + VectorData</td><td class="has-text-align-left" data-align="left">Kernel Memory companion</td><td class="has-text-align-left" data-align="left">Core library directly</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>RAG orchestration</strong></td><td class="has-text-align-left" data-align="left">Manual (VectorData&nbsp;<code>SearchAsync</code>)</td><td class="has-text-align-left" data-align="left">Automatic (Kernel Memory)</td><td class="has-text-align-left" data-align="left">Manual (embed → search → prompt)</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Vector store</strong></td><td class="has-text-align-left" data-align="left"><code>InMemoryVectorStore</code>&nbsp;(built-in)</td><td class="has-text-align-left" data-align="left">Kernel Memory&#8217;s built-in store</td><td class="has-text-align-left" data-align="left">In-memory via LINQ</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Companion packages</strong></td><td class="has-text-align-left" data-align="left"><code>ElBruno.LocalEmbeddings.VectorData</code></td><td class="has-text-align-left" data-align="left"><code>ElBruno.LocalEmbeddings.KernelMemory</code></td><td class="has-text-align-left" data-align="left">None — core only</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Key extension method</strong></td><td class="has-text-align-left" data-align="left"><code>AddLocalEmbeddingsWithInMemoryVectorStore()</code></td><td class="has-text-align-left" data-align="left"><code>.WithLocalEmbeddings()</code></td><td class="has-text-align-left" data-align="left"><code>FindClosest()</code></td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Lines of RAG code</strong></td><td class="has-text-align-left" data-align="left">~20</td><td class="has-text-align-left" data-align="left">~15</td><td class="has-text-align-left" data-align="left">~25</td></tr><tr><td class="has-text-align-left" data-align="left"><strong>Best for</strong></td><td class="has-text-align-left" data-align="left">Search-only, FAQ, no LLM cost</td><td class="has-text-align-left" data-align="left">Turnkey RAG with minimal code</td><td class="has-text-align-left" data-align="left">Full pipeline control</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>My recommendation:</strong></p>



<ul class="wp-block-list">
<li>Start with <strong>RagChat</strong> if you just need semantic search and don&#8217;t want an LLM dependency</li>



<li>Use <strong>RagOllama</strong> if you want a complete RAG system with minimal plumbing</li>



<li>Go with <strong>RagFoundryLocal</strong> if you need to customize every step of the pipeline</li>
</ul>



<p class="wp-block-paragraph">All three share the same foundation:&nbsp;<strong>embeddings generated locally on your machine, no cloud calls, no API keys for the embedding part.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> References and Resources</h2>



<h3 class="wp-block-heading">Project</h3>



<ul class="wp-block-list">
<li><a href="https://github.com/elbruno/elbruno.localembeddings">ElBruno.LocalEmbeddings — GitHub Repository</a></li>



<li><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings">ElBruno.LocalEmbeddings — NuGet Package</a></li>



<li><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.KernelMemory">ElBruno.LocalEmbeddings.KernelMemory — NuGet Package</a></li>



<li><a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings.VectorData">ElBruno.LocalEmbeddings.VectorData — NuGet Package</a></li>
</ul>



<h3 class="wp-block-heading">Sample Source Code</h3>



<ul class="wp-block-list">
<li><a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/RagChat">RagChat sample</a> — VectorData + semantic search</li>



<li><a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/RagOllama">RagOllama sample</a> — Kernel Memory + Ollama</li>



<li><a href="https://github.com/elbruno/elbruno.localembeddings/tree/main/samples/RagFoundryLocal">RagFoundryLocal sample</a> — Manual pipeline + Foundry Local</li>
</ul>



<h3 class="wp-block-heading">External Projects</h3>



<ul class="wp-block-list">
<li><a href="https://devblogs.microsoft.com/dotnet/introducing-microsoft-extensions-ai-preview/">Microsoft.Extensions.AI</a> — Unified AI abstractions for .NET</li>



<li><a href="https://learn.microsoft.com/dotnet/api/microsoft.extensions.vectordata">Microsoft.Extensions.VectorData</a> — Vector store abstractions</li>



<li><a href="https://github.com/microsoft/kernel-memory">Microsoft Kernel Memory</a> — RAG pipeline orchestration</li>



<li><a href="https://ollama.com/">Ollama</a> — Run LLMs locally</li>



<li><a href="https://learn.microsoft.com/ai/foundry/foundry-local/get-started">Microsoft AI Foundry Local</a> — Run AI models locally with OpenAI-compatible APIs</li>



<li><a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">sentence-transformers/all-MiniLM-L6-v2</a> — Default embedding model (HuggingFace)</li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/14/%f0%9f%a7%a0-building-rag-in-net-with-local-embeddings-3-approaches-zero-cloud-calls/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37208</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/small.png">
			<media:title type="html">small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>Local Embeddings in .NET — The Easy Way</title>
		<link>https://elbruno.com/2026/02/13/local-embeddings-in-net-the-easy-way/</link>
					<comments>https://elbruno.com/2026/02/13/local-embeddings-in-net-the-easy-way/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Fri, 13 Feb 2026 16:03:21 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[Embeddings]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[NuGet]]></category>
		<category><![CDATA[rag]]></category>
		<category><![CDATA[technology]]></category>
		<category><![CDATA[Video]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37199</guid>

					<description><![CDATA[Hi! If you’re running local LLMs using: At some point you’ll want to experiment with RAG. And that means one thing: 👉 You need embeddings. During production, you’ll probably rely on managed services and vector databases.But during experimentation? You just want something simple. That’s why I created: 📦 ElBruno.LocalEmbeddingsNuGet: https://www.nuget.org/packages/ElBruno.LocalEmbeddingsRepo: https://github.com/elbruno/elbruno.localembeddings/ It allows you to [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">If you’re running local LLMs using:</p>



<ul class="wp-block-list">
<li><strong>Foundry Local</strong></li>



<li><strong>Ollama</strong></li>



<li>Models like <strong>Phi</strong>, <strong>Qwen</strong>, or Llama</li>
</ul>



<p class="wp-block-paragraph">At some point you’ll want to experiment with <strong>RAG</strong>. And that means one thing: <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> You need embeddings.</p>



<p class="wp-block-paragraph">During production, you’ll probably rely on managed services and vector databases.<br />But during experimentation?</p>



<p class="wp-block-paragraph">You just want something simple. That’s why I created:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>ElBruno.LocalEmbeddings</strong><br />NuGet: <a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings">https://www.nuget.org/packages/ElBruno.LocalEmbeddings</a><br />Repo: <a href="https://github.com/elbruno/elbruno.localembeddings/">https://github.com/elbruno/elbruno.localembeddings/</a></p>



<p class="wp-block-paragraph">It allows you to generate embeddings locally in .NET with minimal setup.</p>



<p class="wp-block-paragraph">Let’s take a look.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 1&#x20e3; Generate a Single Embedding</h1>



<p class="wp-block-paragraph">Install the package:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet add package ElBruno.LocalEmbeddings</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then use it:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Create the generator with default settings</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">generator</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">LocalEmbeddingGenerator</span>(</div><div class="cm-line">    <span class="tok-keyword">new</span> <span class="tok-variableName">ElBruno</span>.<span class="tok-variableName">LocalEmbeddings</span>.<span class="tok-variableName">Options</span>.<span class="tok-variableName">LocalEmbeddingsOptions</span>());</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Generate a single embedding</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">embedding</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">generator</span>.<span class="tok-variableName">GenerateEmbeddingAsync</span>(<span class="tok-string">&quot;Hello, world!&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;Dimensions: {embedding.Vector.Length}&quot;</span>); <span class="tok-comment">// 384</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Don&apos;t forget to dispose when done</span></div><div class="cm-line"><span class="tok-variableName">generator</span>.<span class="tok-variableName">Dispose</span>();</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That’s it. The first time it runs, it downloads the model locally and caches it.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 2&#x20e3; Generate Multiple Embeddings (Batch Mode)</h1>



<p class="wp-block-paragraph">You can also generate embeddings in batch:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">texts</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>[]</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-string">&quot;First document&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Second document&quot;</span>,</div><div class="cm-line">    <span class="tok-string">&quot;Third document&quot;</span></div><div class="cm-line">};</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">embeddings</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">generator</span>.<span class="tok-variableName">GenerateAsync</span>(<span class="tok-variableName">texts</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">for</span> (<span class="tok-typeName">int</span> <span class="tok-variableName">i</span> <span class="tok-operator">=</span> <span class="tok-number">0</span>; <span class="tok-variableName">i</span> <span class="tok-operator">&lt;</span> <span class="tok-variableName">texts</span>.<span class="tok-variableName">Length</span>; <span class="tok-variableName">i</span><span class="tok-operator">++</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-variableName">Console</span>.<span class="tok-variableName">WriteLine</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;Embedding for &apos;{texts[i]}&apos;: [{string.Join(&quot;</span>, <span class="tok-string">&quot;{embeddings[i].Vector.ToArray().Take(3))}]&quot;</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Each text gets its own vector.</p>



<p class="wp-block-paragraph">This is the foundation for:</p>



<ul class="wp-block-list">
<li>Semantic search</li>



<li>Similarity comparison</li>



<li>Document ranking</li>



<li>RAG pipelines</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Bonus: Cosine Similarity Example</h1>



<p class="wp-block-paragraph">Once you have vectors, you can compare them, check the main <a href="https://github.com/elbruno/elbruno.localembeddings/blob/main/samples/ConsoleApp/Program.cs">console sample code here </a>to learn more. </p>



<p class="wp-block-paragraph">This is exactly what you need to start building a minimal <strong>local RAG system</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Why This Exists</h1>



<p class="wp-block-paragraph">This package is not trying to replace production infrastructure like Microsoft Foundry managed services.</p>



<p class="wp-block-paragraph">It’s about:</p>



<ul class="wp-block-list">
<li>Learning faster</li>



<li>Prototyping faster</li>



<li>Experimenting locally</li>



<li>Removing friction</li>
</ul>



<p class="wp-block-paragraph">If you&#8217;re running local LLMs with Foundry Local or Ollama and just want to test RAG — this helps.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Try It Today</h1>



<ul class="wp-block-list">
<li>NuGet: <a href="https://www.nuget.org/packages/ElBruno.LocalEmbeddings">https://www.nuget.org/packages/ElBruno.LocalEmbeddings</a></li>



<li>GitHub Repo: <a href="https://github.com/elbruno/elbruno.localembeddings/">https://github.com/elbruno/elbruno.localembeddings/</a></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h1 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Want to See It in Action?</h1>



<p class="wp-block-paragraph">I recorded a <strong>10-minute video walkthrough</strong> where I:</p>



<ul class="wp-block-list">
<li>Explain embeddings in simple terms</li>



<li>Show both code demos</li>



<li>Compare similarity results</li>



<li>Talk about using it with Foundry Local and Ollama</li>



<li>Position it for experimentation vs production</li>
</ul>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Watch the video here:<br /></p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/0rRgSQWlVm8?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>



<p class="wp-block-paragraph">Let me know what you’d like next:</p>



<ul class="wp-block-list">
<li>A minimal local RAG starter template</li>



<li>Full Foundry Local + Embeddings demo</li>



<li>Ollama + RAG end-to-end sample</li>



<li>This running in a Raspberry Pi (why not?)</li>
</ul>



<p class="wp-block-paragraph">Let’s build smarter AI in .NET — with less friction <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f499.png" alt="💙" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/13/local-embeddings-in-net-the-easy-way/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37199</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2026/02/small.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/02/small.png">
			<media:title type="html">small</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>Building GitHub Copilot Agents in C# with Microsoft Agent Framework</title>
		<link>https://elbruno.com/2026/02/09/building-github-copilot-agents-in-c-with-microsoft-agent-framework/</link>
					<comments>https://elbruno.com/2026/02/09/building-github-copilot-agents-in-c-with-microsoft-agent-framework/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Mon, 09 Feb 2026 16:52:51 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Artificial Intelligence]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[Copilot]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[GitHub Copilot]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37194</guid>

					<description><![CDATA[GitHub Copilot just crossed a very interesting line. It’s no longer “just” helping you write code — it can now run as an agent, with goals, tools, and autonomy, using Microsoft Agent Framework (MAF). 🎥 Watch the full video here: In the video, I walk through three simple C# samples showing how Copilot can be [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">GitHub Copilot just crossed a very interesting line.</p>



<p class="wp-block-paragraph">It’s no longer “just” helping you write code — <strong>it can now run as an agent</strong>, with goals, tools, and autonomy, using <strong>Microsoft Agent Framework (MAF)</strong>.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a5.png" alt="🎥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Watch the full video here:</strong></p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/rkePTvuoAew?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>



<p class="wp-block-paragraph">In the video, I walk through <strong>three simple C# samples</strong> showing how Copilot can be used <em>as an agent</em>, not just a coding assistant.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">From autocomplete to autonomy</h2>



<p class="wp-block-paragraph">The mental model shift is simple:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Copilot as a tool → prompt → suggestion</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Copilot as an agent → goal → plan → act</li>
</ul>



<p class="wp-block-paragraph">With Microsoft Agent Framework, GitHub Copilot becomes part of your <strong>agent runtime</strong>, not just your editor UI.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Sample 1 – Creating a Copilot-backed agent</h2>



<p class="wp-block-paragraph">At its core, you define an agent and tell it <strong>what it is allowed to do</strong>.</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">GitHub</span>.<span class="tok-variableName">Copilot</span>.<span class="tok-variableName">SDK</span>;</div><div class="cm-line"><span class="tok-keyword">using</span> <span class="tok-variableName">Microsoft</span>.<span class="tok-variableName">Agents</span>.<span class="tok-variableName">AI</span>;</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-keyword">using</span> <span class="tok-variableName">CopilotClient</span> <span class="tok-variableName">copilotClient</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>();</div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-variableName">copilotClient</span>.<span class="tok-variableName">StartAsync</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">AIAgent</span> <span class="tok-variableName">agent</span> <span class="tok-operator">=</span> <span class="tok-variableName">copilotClient</span>.<span class="tok-variableName">AsAIAgent</span>(</div><div class="cm-line">    <span class="tok-variableName">instructions</span>: <span class="tok-string">&quot;You are a helpful agent.&quot;</span>);</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">No hacks.<br />No wrappers.<br />This agent is powered directly by GitHub Copilot.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Sample 2 – Giving the agent a goal</h2>



<p class="wp-block-paragraph">Instead of prompting, you assign <strong>tasks</strong>.</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-variableName">agent</span>.<span class="tok-variableName">RunAsync</span>(<span class="tok-string">&quot;&quot;&quot;</span></div><div class="cm-line"><span class="tok-variableName">Review</span> <span class="tok-keyword">this</span> <span class="tok-variableName">C#</span> <span class="tok-variableName">project</span> <span class="tok-variableName">and</span> <span class="tok-variableName">explain</span> <span class="tok-variableName">what</span> <span class="tok-variableName">it</span> <span class="tok-variableName">does</span>.</div><div class="cm-line"><span class="tok-variableName">Focus</span> <span class="tok-variableName">on</span> <span class="tok-variableName">architecture</span> <span class="tok-variableName">and</span> <span class="tok-variableName">main</span> <span class="tok-variableName">responsibilities</span>.</div><div class="cm-line"><span class="tok-string">&quot;&quot;&quot;);</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This is where Copilot stops responding and starts <strong>working</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Sample 3 – Applying it to real dev workflows</h2>



<p class="wp-block-paragraph">In the video, I show how the same agent can be used for things like:</p>



<ul class="wp-block-list">
<li>Understanding unfamiliar repositories</li>



<li>Explaining legacy code</li>



<li>Supporting real developer workflows</li>
</ul>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-variableName">agent</span>.<span class="tok-variableName">RunAsync</span>(<span class="tok-string">&quot;&quot;&quot;</span></div><div class="cm-line"><span class="tok-variableName">Analyze</span> <span class="tok-keyword">this</span> <span class="tok-variableName">repository</span> <span class="tok-variableName">and</span> <span class="tok-variableName">suggest</span> <span class="tok-variableName">improvements</span>.</div><div class="cm-line"><span class="tok-variableName">Create</span> <span class="tok-variableName">a</span> <span class="tok-variableName">summary</span> <span class="tok-variableName">suitable</span> <span class="tok-keyword">for</span> <span class="tok-variableName">a</span> <span class="tok-variableName">pull</span> <span class="tok-variableName">request</span> <span class="tok-variableName">description</span>.</div><div class="cm-line"><span class="tok-variableName">Run</span> <span class="tok-variableName">the</span> <span class="tok-variableName">analisys</span> <span class="tok-keyword">using</span> <span class="tok-variableName">CODEX</span> <span class="tok-variableName">and</span> <span class="tok-variableName">OPUS</span>.</div><div class="cm-line"><span class="tok-string">&quot;&quot;&quot;);</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This feels much closer to a <strong>junior developer teammate</strong> than a chat window.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Why this matters for .NET developers</h2>



<p class="wp-block-paragraph">If you’re building in C# today:</p>



<ul class="wp-block-list">
<li>You already know GitHub Copilot</li>



<li>You already write .NET services</li>



<li>You already work with repositories and PRs</li>
</ul>



<p class="wp-block-paragraph">Microsoft Agent Framework + Copilot is the <strong>shortest path</strong> from AI ideas to real systems.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Resources</h2>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d8.png" alt="📘" class="wp-smiley" style="height: 1em; max-height: 1em;" /> GitHub Copilot Agent docs (C#):<br /><a href="https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/agent-types/github-copilot-agent?pivots=programming-language-csharp">https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/agent-types/github-copilot-agent?pivots=programming-language-csharp</a></li>
</ul>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/09/building-github-copilot-agents-in-c-with-microsoft-agent-framework/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37194</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>&#129302; Never Lose Your AI Agent’s Train of Thought</title>
		<link>https://elbruno.com/2026/02/03/%f0%9f%a4%96-never-lose-your-ai-agents-train-of-thought/</link>
					<comments>https://elbruno.com/2026/02/03/%f0%9f%a4%96-never-lose-your-ai-agents-train-of-thought/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Tue, 03 Feb 2026 15:31:04 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[.Net]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[MAF]]></category>
		<category><![CDATA[Microsoft Agent Framework]]></category>
		<category><![CDATA[Ollama]]></category>
		<category><![CDATA[Persisting Memory]]></category>
		<category><![CDATA[Redis]]></category>
		<category><![CDATA[technology]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37180</guid>

					<description><![CDATA[Persisting Microsoft Agent Framework Sessions with ASP.NET, Redis &#38; Blazor Have you ever built a chat app where the AI forgets what you said five seconds ago? 😅That&#8217;s the classic stateless API problem — and today we&#8217;re fixing that in .NET using the Microsoft Agent Framework (MAF + Ollama), persistent agent sessions (Redis), and a Blazor frontend so you can see [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-never-lose-your-ai-agents-train-of-thought"></a></p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<h3 class="wp-block-heading">Persisting Microsoft Agent Framework Sessions with ASP.NET, Redis &amp; Blazor<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#persisting-microsoft-agent-framework-sessions-with-aspnet-redis--blazor"></a></h3>



<p class="wp-block-paragraph">Have you ever built a chat app where the AI forgets what you said <em>five seconds ago</em>? <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f605.png" alt="😅" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br />That&#8217;s the classic stateless API problem — and today we&#8217;re fixing that in .NET using the <strong>Microsoft Agent Framework (MAF + Ollama)</strong>, <strong>persistent agent sessions</strong> (Redis), and a <strong>Blazor frontend</strong> so you can <em>see memory working live</em>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Problem: Stateless APIs vs Real Conversations<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-the-problem-stateless-apis-vs-real-conversations"></a></h2>



<p class="wp-block-paragraph">Classic Web APIs work like this:</p>



<p class="wp-block-paragraph"><strong>Request → Response → Bye <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></p>



<p class="wp-block-paragraph">That&#8217;s great for CRUD operations. It&#8217;s&nbsp;<em>terrible</em>&nbsp;for conversations.</p>



<p class="wp-block-paragraph">Conversational agents are&nbsp;<strong>multi‑turn</strong>. They need context. Without it, you&#8217;ll see:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Repeated questions</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f937.png" alt="🤷" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Inconsistent answers</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9f9.png" alt="🧹" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Personality resets</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f62c.png" alt="😬" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Users rage‑quitting your app</li>
</ul>



<figure class="wp-block-image"><a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/images/scene1-1280x720.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/raw/main/docs/images/scene1-1280x720.png" alt="Stateless APIs vs Multi-turn Chat — The AI keeps asking &quot;What's your name?&quot; because it has no memory" /></a></figure>



<p class="wp-block-paragraph">In MAF, this happens because&nbsp;<strong>the agent itself is stateless</strong>. The agent is just the brain — it processes requests and generates responses. But without something to&nbsp;<em>hold</em>&nbsp;the conversation history between requests, every message starts fresh.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f392.png" alt="🎒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Mental Model: Agent ≠ Memory<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-the-mental-model-agent--memory"></a></h2>



<p class="wp-block-paragraph">This is the most important concept in this post, so let me make it crystal clear:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Component</th><th>What it does</th><th>Analogy</th></tr></thead><tbody><tr><td><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Agent</strong></td><td>Stateless brain that processes messages</td><td>The thinker</td></tr><tr><td><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f392.png" alt="🎒" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>AgentSession</strong></td><td>Memory backpack holding conversation history</td><td>The backpack</td></tr><tr><td><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4e6.png" alt="📦" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Persisted Store</strong></td><td>Where memory survives between requests</td><td>The safe</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">If you don&#8217;t persist the backpack, your agent has&nbsp;<strong>goldfish memory</strong>&nbsp;<img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f41f.png" alt="🐟" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>



<figure class="wp-block-image"><a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/images/scene3-1280x720.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/raw/main/docs/images/scene3-1280x720.png" alt="Agent = Stateless Brain, AgentSession = Memory Backpack — Without persistence, you get goldfish memory" /></a></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"></blockquote>



<p class="wp-block-paragraph">MAF fully supports serializing and restoring&nbsp;<code>AgentSession</code>. This is what makes proper multi‑turn conversations possible. The framework handles the conversation tracking&nbsp;<em>inside</em>&nbsp;the session — you just need to save and load it between requests.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Architecture Overview<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#%EF%B8%8F-architecture-overview"></a></h2>



<p class="wp-block-paragraph">Here&#8217;s the flow we&#8217;re building:</p>



<ol class="wp-block-list">
<li><strong>Client</strong>&nbsp;sends&nbsp;<code>conversationId</code>&nbsp;+ message</li>



<li><strong>API</strong>&nbsp;loads&nbsp;<code>AgentSession</code>&nbsp;from the store (Redis or in‑memory)</li>



<li><strong>Agent</strong>&nbsp;runs with that session</li>



<li><strong>Updated session</strong>&nbsp;is saved back to the store</li>



<li><strong>Response</strong>&nbsp;is returned to the client</li>
</ol>



<figure class="wp-block-image"><a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/images/scene2-1280x720.png" target="_blank" rel="noreferrer noopener"><img src="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/raw/main/docs/images/scene2-1280x720.png" alt="Architecture Flow — ID → Load Session → Run Agent → Save Session" /></a></figure>



<p class="wp-block-paragraph">This pattern is production-ready because:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Horizontal scaling</strong>: Multiple API instances share state via Redis</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Restart resilience</strong>: Sessions survive app restarts</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Real traffic</strong>: Clients can hit any instance and get consistent behavior</li>
</ul>



<p class="wp-block-paragraph">The magic happens in that simple loop:&nbsp;<strong>Load → Run → Save</strong>. Everything else is just plumbing.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tech Stack: .NET Aspire + Ollama + Redis<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#%EF%B8%8F-tech-stack-net-aspire--ollama--redis"></a></h2>



<p class="wp-block-paragraph">Before we dive into code, here&#8217;s what orchestrates the whole solution:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// AppHost.cs — .NET Aspire orchestration</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">builder</span> <span class="tok-operator">=</span> <span class="tok-variableName">DistributedApplication</span>.<span class="tok-variableName">CreateBuilder</span>(<span class="tok-variableName">args</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Redis for session persistence</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">cache</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddRedis</span>(<span class="tok-string">&quot;cache&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Ollama for local AI (no cloud required!)</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">ollama</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddOllama</span>(<span class="tok-string">&quot;ollama&quot;</span>).<span class="tok-variableName">WithDataVolume</span>();</div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">ollamaModel</span> <span class="tok-operator">=</span> <span class="tok-variableName">ollama</span>.<span class="tok-variableName">AddModel</span>(<span class="tok-string">&quot;chat-model&quot;</span>, <span class="tok-string">&quot;llama3.2:1b&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// API with Redis and Ollama references</span></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">api</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">AddProject</span><span class="tok-operator">&lt;</span><span class="tok-variableName">Projects</span>.<span class="tok-variableName">MafStatefulApi_Api</span><span class="tok-operator">&gt;</span>(<span class="tok-string">&quot;api&quot;</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithReference</span>(<span class="tok-variableName">cache</span>).<span class="tok-variableName">WaitFor</span>(<span class="tok-variableName">cache</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithReference</span>(<span class="tok-variableName">ollamaModel</span>).<span class="tok-variableName">WaitFor</span>(<span class="tok-variableName">ollamaModel</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// Blazor Web UI</span></div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">AddProject</span><span class="tok-operator">&lt;</span><span class="tok-variableName">Projects</span>.<span class="tok-variableName">MafStatefulApi_Web</span><span class="tok-operator">&gt;</span>(<span class="tok-string">&quot;web&quot;</span>)</div><div class="cm-line">    .<span class="tok-variableName">WithReference</span>(<span class="tok-variableName">api</span>).<span class="tok-variableName">WaitFor</span>(<span class="tok-variableName">api</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">Build</span>().<span class="tok-variableName">Run</span>();</div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The ASP.NET Core Minimal API<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-the-aspnet-core-minimal-api"></a></h2>



<h3 class="wp-block-heading">Request &amp; Response Models<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#request--response-models"></a></h3>



<p class="wp-block-paragraph">Simple records, nothing fancy:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">record</span> <span class="tok-variableName tok-definition">ChatRequest</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-variableName">ConversationId</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">required</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Message</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">record</span> <span class="tok-variableName tok-definition">ChatResponse</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">required</span> <span class="tok-typeName">string</span> <span class="tok-variableName">ConversationId</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">required</span> <span class="tok-typeName">string</span> <span class="tok-variableName">Answer</span> { <span class="tok-keyword">get</span>; <span class="tok-keyword">init</span>; }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">The&nbsp;<code>/chat</code>&nbsp;Endpoint<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#the-chat-endpoint"></a></h3>



<p class="wp-block-paragraph">Here&#8217;s where the session magic lives:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">group</span>.<span class="tok-variableName">MapPost</span>(<span class="tok-string">&quot;/chat&quot;</span>, <span class="tok-keyword">async</span> (</div><div class="cm-line">    <span class="tok-variableName">ChatRequest</span> <span class="tok-variableName">request</span>,</div><div class="cm-line">    <span class="tok-variableName">AgentRunner</span> <span class="tok-variableName">agentRunner</span>,</div><div class="cm-line">    <span class="tok-variableName">ILogger</span><span class="tok-operator">&lt;</span><span class="tok-variableName">Program</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">logger</span>,</div><div class="cm-line">    <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-comment">// Generate a new conversation ID if not provided</span></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">conversationId</span> <span class="tok-operator">=</span> <span class="tok-variableName">request</span>.<span class="tok-variableName">ConversationId</span> <span class="tok-operator">??</span> </div><div class="cm-line">      <span class="tok-variableName">$</span><span class="tok-string">&quot;{DateTime.Now:HH:mm:ss}-{Guid.NewGuid()}&quot;</span>;</div><div class="cm-line">    </div><div class="cm-line">    <span class="tok-variableName">logger</span>.<span class="tok-variableName">LogInformation</span>(</div><div class="cm-line">        <span class="tok-string">&quot;Chat request received for conversation {ConversationId}&quot;</span>,</div><div class="cm-line">        <span class="tok-variableName">conversationId</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">answer</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">agentRunner</span>.<span class="tok-variableName">RunAsync</span>(</div><div class="cm-line">        <span class="tok-variableName">conversationId</span>,</div><div class="cm-line">        <span class="tok-variableName">request</span>.<span class="tok-variableName">Message</span>,</div><div class="cm-line">        <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">ChatResponse</span></div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">ConversationId</span> <span class="tok-operator">=</span> <span class="tok-variableName">conversationId</span>,</div><div class="cm-line">        <span class="tok-variableName">Answer</span> <span class="tok-operator">=</span> <span class="tok-variableName">answer</span></div><div class="cm-line">    });</div><div class="cm-line">});</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Notice how clean this is. The endpoint doesn&#8217;t deal with sessions directly — that complexity is encapsulated in the&nbsp;<code>AgentRunner</code>. Following the single responsibility principle keeps the endpoint focused on HTTP concerns.</p>



<h3 class="wp-block-heading">Bonus Endpoints<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#bonus-endpoints"></a></h3>



<p class="wp-block-paragraph">The API also includes session management endpoints:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-comment">// Reset a conversation</span></div><div class="cm-line"><span class="tok-keyword">group</span>.<span class="tok-variableName">MapPost</span>(<span class="tok-string">&quot;/reset/{conversationId}&quot;</span>, <span class="tok-keyword">async</span> (</div><div class="cm-line">    <span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>,</div><div class="cm-line">    <span class="tok-variableName">IAgentSessionStore</span> <span class="tok-variableName">sessionStore</span>,</div><div class="cm-line">    <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">await</span> <span class="tok-variableName">sessionStore</span>.<span class="tok-variableName">DeleteAsync</span>(<span class="tok-variableName">conversationId</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-keyword">new</span> { <span class="tok-variableName">message</span> <span class="tok-operator">=</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;Conversation {conversationId} has been reset.&quot;</span> });</div><div class="cm-line">});</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// List all active sessions</span></div><div class="cm-line"><span class="tok-keyword">group</span>.<span class="tok-variableName">MapGet</span>(<span class="tok-string">&quot;/sessions&quot;</span>, <span class="tok-keyword">async</span> (</div><div class="cm-line">    <span class="tok-variableName">IAgentSessionStore</span> <span class="tok-variableName">sessionStore</span>,</div><div class="cm-line">    <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span>) <span class="tok-operator">=&gt;</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">sessions</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">sessionStore</span>.<span class="tok-variableName">ListSessionsAsync</span>(<span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-variableName">Results</span>.<span class="tok-variableName">Ok</span>(<span class="tok-keyword">new</span> { <span class="tok-variableName">sessions</span> });</div><div class="cm-line">});</div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The AgentRunner: Where Memory Happens<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-the-agentrunner-where-memory-happens"></a></h2>



<p class="wp-block-paragraph">The&nbsp;<code>AgentRunner</code>&nbsp;is the orchestrator. It handles the&nbsp;<strong>Load → Run → Save</strong>&nbsp;cycle:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">AgentRunner</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">AIAgent</span> <span class="tok-variableName">_agent</span>;</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">IAgentSessionStore</span> <span class="tok-variableName">_sessionStore</span>;</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">ILogger</span><span class="tok-operator">&lt;</span><span class="tok-variableName">AgentRunner</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">_logger</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">static</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">JsonSerializerOptions</span> <span class="tok-variableName">JsonOptions</span> <span class="tok-operator">=</span> <span class="tok-variableName">JsonSerializerOptions</span>.<span class="tok-variableName">Web</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-variableName">AgentRunner</span>(</div><div class="cm-line">        [<span class="tok-variableName">FromKeyedServices</span>(<span class="tok-string">&quot;AssistantAgent&quot;</span>)] <span class="tok-variableName">AIAgent</span> <span class="tok-variableName">agent</span>,</div><div class="cm-line">        <span class="tok-variableName">IAgentSessionStore</span> <span class="tok-variableName">sessionStore</span>,</div><div class="cm-line">        <span class="tok-variableName">ILogger</span><span class="tok-operator">&lt;</span><span class="tok-variableName">AgentRunner</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">logger</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">_agent</span> <span class="tok-operator">=</span> <span class="tok-variableName">agent</span>;</div><div class="cm-line">        <span class="tok-variableName">_sessionStore</span> <span class="tok-operator">=</span> <span class="tok-variableName">sessionStore</span>;</div><div class="cm-line">        <span class="tok-variableName">_logger</span> <span class="tok-operator">=</span> <span class="tok-variableName">logger</span>;</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">RunAsync</span>(</div><div class="cm-line">        <span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>,</div><div class="cm-line">        <span class="tok-typeName">string</span> <span class="tok-variableName">userMessage</span>,</div><div class="cm-line">        <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-comment">// 1&#x20e3; Load or create the session</span></div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">session</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">LoadOrCreateSessionAsync</span>(<span class="tok-variableName">conversationId</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-comment">// 2&#x20e3; Run the agent with the message and session</span></div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">response</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">_agent</span>.<span class="tok-variableName">RunAsync</span>(<span class="tok-variableName">userMessage</span>, <span class="tok-variableName">session</span>, <span class="tok-variableName">cancellationToken</span>: <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">answer</span> <span class="tok-operator">=</span> <span class="tok-variableName">response</span>.<span class="tok-variableName">Text</span> <span class="tok-operator">??</span> <span class="tok-typeName">string</span>.<span class="tok-variableName">Empty</span>;</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-comment">// 3&#x20e3; Save the updated session</span></div><div class="cm-line">        <span class="tok-keyword">await</span> <span class="tok-variableName">SaveSessionAsync</span>(<span class="tok-variableName">conversationId</span>, <span class="tok-variableName">session</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">answer</span>;</div><div class="cm-line">    }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The&nbsp;<code>AgentSession</code>&nbsp;object accumulates the conversation history automatically. The agent adds each turn to the session, so by the time we save it, all the context is captured.</p>



<h3 class="wp-block-heading">Deserializing Sessions (The Tricky Part)<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#deserializing-sessions-the-tricky-part"></a></h3>



<p class="wp-block-paragraph">Loading a session from storage requires deserialization. MAF provides <code>DeserializeSessionAsync</code> to reconstruct the session from JSON:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">private</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-variableName">AgentSession</span><span class="tok-operator">&gt;</span> <span class="tok-variableName tok-definition">LoadOrCreateSessionAsync</span>(</div><div class="cm-line">    <span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>,</div><div class="cm-line">    <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">serializedSession</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">_sessionStore</span>.<span class="tok-variableName">GetAsync</span>(<span class="tok-variableName">conversationId</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">    </div><div class="cm-line">    <span class="tok-keyword">if</span> (<span class="tok-variableName">serializedSession</span> <span class="tok-keyword">is</span> <span class="tok-variableName">not</span> <span class="tok-atom">null</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">try</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">jsonElement</span> <span class="tok-operator">=</span> <span class="tok-variableName">JsonSerializer</span>.<span class="tok-variableName">Deserialize</span><span class="tok-operator">&lt;</span><span class="tok-variableName">JsonElement</span><span class="tok-operator">&gt;</span>(</div><div class="cm-line">                <span class="tok-variableName">serializedSession</span>, <span class="tok-variableName">JsonOptions</span>);</div><div class="cm-line">            <span class="tok-keyword">return</span> <span class="tok-keyword">await</span> <span class="tok-variableName">_agent</span>.<span class="tok-variableName">DeserializeSessionAsync</span>(</div><div class="cm-line">                <span class="tok-variableName">jsonElement</span>, <span class="tok-variableName">JsonOptions</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">        }</div><div class="cm-line">        <span class="tok-keyword">catch</span> (<span class="tok-variableName">Exception</span> <span class="tok-variableName">ex</span>)</div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">_logger</span>.<span class="tok-variableName">LogWarning</span>(<span class="tok-variableName">ex</span>,</div><div class="cm-line">                <span class="tok-string">&quot;Failed to deserialize session for {ConversationId}, creating new&quot;</span>,</div><div class="cm-line">                <span class="tok-variableName">conversationId</span>);</div><div class="cm-line">        }</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-comment">// No existing session — create a fresh one</span></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-keyword">await</span> <span class="tok-variableName">_agent</span>.<span class="tok-variableName">GetNewSessionAsync</span>(<span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Serializing Sessions<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#serializing-sessions"></a></h3>



<p class="wp-block-paragraph">Saving is straightforward — MAF sessions serialize to JSON:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">private</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span> <span class="tok-variableName tok-definition">SaveSessionAsync</span>(</div><div class="cm-line">    <span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>,</div><div class="cm-line">    <span class="tok-variableName">AgentSession</span> <span class="tok-variableName">session</span>,</div><div class="cm-line">    <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span>)</div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">serializedElement</span> <span class="tok-operator">=</span> <span class="tok-variableName">session</span>.<span class="tok-variableName">Serialize</span>(<span class="tok-variableName">JsonOptions</span>);</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">serialized</span> <span class="tok-operator">=</span> <span class="tok-variableName">serializedElement</span>.<span class="tok-variableName">GetRawText</span>();</div><div class="cm-line">    </div><div class="cm-line">    <span class="tok-keyword">await</span> <span class="tok-variableName">_sessionStore</span>.<span class="tok-variableName">SetAsync</span>(<span class="tok-variableName">conversationId</span>, <span class="tok-variableName">serialized</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5c4.png" alt="🗄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Session Store Implementations<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#%EF%B8%8F-session-store-implementations"></a></h2>



<p class="wp-block-paragraph">The&nbsp;<code>IAgentSessionStore</code>&nbsp;interface abstracts storage, making it easy to swap implementations:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">interface</span> <span class="tok-variableName tok-definition">IAgentSessionStore</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">?&gt;</span> <span class="tok-variableName">GetAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>);</div><div class="cm-line">    <span class="tok-typeName">Task</span> <span class="tok-variableName">SetAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">serializedThread</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>);</div><div class="cm-line">    <span class="tok-typeName">Task</span> <span class="tok-variableName">DeleteAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>);</div><div class="cm-line">    <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-variableName">IEnumerable</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;&gt;</span> <span class="tok-variableName">ListSessionsAsync</span>(<span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>);</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Redis Implementation (Production)<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#redis-implementation-production"></a></h3>



<p class="wp-block-paragraph">For production, use Redis. It survives restarts, scales horizontally, and is battle-tested:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">RedisAgentSessionStore</span> : <span class="tok-variableName">IAgentSessionStore</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">IDistributedCache</span> <span class="tok-variableName">_cache</span>;</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-typeName">TimeSpan</span> <span class="tok-variableName">_sessionTtl</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">?&gt;</span> <span class="tok-variableName">GetAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">key</span> <span class="tok-operator">=</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;maf:sessions:{conversationId}&quot;</span>;</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-keyword">await</span> <span class="tok-variableName">_cache</span>.<span class="tok-variableName">GetStringAsync</span>(<span class="tok-variableName">key</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span> <span class="tok-variableName">SetAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">serializedThread</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">key</span> <span class="tok-operator">=</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;maf:sessions:{conversationId}&quot;</span>;</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">options</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">DistributedCacheEntryOptions</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">SlidingExpiration</span> <span class="tok-operator">=</span> <span class="tok-variableName">_sessionTtl</span>  <span class="tok-comment">// Default: 30 minutes</span></div><div class="cm-line">        };</div><div class="cm-line">        </div><div class="cm-line">        <span class="tok-keyword">await</span> <span class="tok-variableName">_cache</span>.<span class="tok-variableName">SetStringAsync</span>(<span class="tok-variableName">key</span>, <span class="tok-variableName">serializedThread</span>, <span class="tok-variableName">options</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span> <span class="tok-variableName">DeleteAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">key</span> <span class="tok-operator">=</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;maf:sessions:{conversationId}&quot;</span>;</div><div class="cm-line">        <span class="tok-keyword">await</span> <span class="tok-variableName">_cache</span>.<span class="tok-variableName">RemoveAsync</span>(<span class="tok-variableName">key</span>, <span class="tok-variableName">cancellationToken</span>);</div><div class="cm-line">    }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The sliding expiration is important — sessions expire after 30 minutes of inactivity (configurable via&nbsp;<code>SessionTtlMinutes</code>&nbsp;in configuration).</p>



<h3 class="wp-block-heading">In-Memory Implementation (Development)<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#in-memory-implementation-development"></a></h3>



<p class="wp-block-paragraph">For local development or single-instance deployments, the in-memory store works great:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">InMemoryAgentSessionStore</span> : <span class="tok-variableName">IAgentSessionStore</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">IMemoryCache</span> <span class="tok-variableName">_cache</span>;</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">ConcurrentDictionary</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span>, <span class="tok-typeName">bool</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">_sessionKeys</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>();</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">?&gt;</span> <span class="tok-variableName">GetAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">key</span> <span class="tok-operator">=</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;maf:sessions:{conversationId}&quot;</span>;</div><div class="cm-line">        <span class="tok-variableName">_cache</span>.<span class="tok-variableName">TryGetValue</span>(<span class="tok-variableName">key</span>, <span class="tok-keyword">out</span> <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-keyword">value</span>);</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-typeName">Task</span>.<span class="tok-variableName">FromResult</span>(<span class="tok-keyword">value</span>);</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-typeName">Task</span> <span class="tok-variableName">SetAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>, <span class="tok-typeName">string</span> <span class="tok-variableName">serializedThread</span>, <span class="tok-variableName">CancellationToken</span> <span class="tok-variableName">cancellationToken</span> <span class="tok-operator">=</span> <span class="tok-keyword">default</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">key</span> <span class="tok-operator">=</span> <span class="tok-variableName">$</span><span class="tok-string">&quot;maf:sessions:{conversationId}&quot;</span>;</div><div class="cm-line">        <span class="tok-variableName">_cache</span>.<span class="tok-variableName">Set</span>(<span class="tok-variableName">key</span>, <span class="tok-variableName">serializedThread</span>, <span class="tok-keyword">new</span> <span class="tok-variableName">MemoryCacheEntryOptions</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">SlidingExpiration</span> <span class="tok-operator">=</span> <span class="tok-variableName">_sessionTtl</span></div><div class="cm-line">        });</div><div class="cm-line">        <span class="tok-variableName">_sessionKeys</span>.<span class="tok-variableName">TryAdd</span>(<span class="tok-variableName">conversationId</span>, <span class="tok-atom">true</span>);  <span class="tok-comment">// Track for listing</span></div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-typeName">Task</span>.<span class="tok-variableName">CompletedTask</span>;</div><div class="cm-line">    }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f5a5.png" alt="🖥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> The Blazor Web UI: See Memory in Action<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#%EF%B8%8F-the-blazor-web-ui-see-memory-in-action"></a></h2>



<p class="wp-block-paragraph">Documentation is one thing.&nbsp;<em>Seeing</em>&nbsp;it work is another.</p>



<p class="wp-block-paragraph">The repo includes a&nbsp;<strong>Blazor Server UI</strong>&nbsp;that demonstrates session persistence in real-time. No abstract explanations — just chat, refresh the page, and continue where you left off.</p>



<h3 class="wp-block-heading">Key Features<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#key-features"></a></h3>



<ul class="wp-block-list">
<li><strong>Session Selector</strong>: Dropdown to switch between active conversations</li>



<li><strong>Real-time Chat</strong>: Interactive message interface with typing indicators</li>



<li><strong>Session Management</strong>: Create, switch, and reset conversations</li>



<li><strong>Visual Feedback</strong>: See the conversation ID stay consistent across page reloads</li>
</ul>



<h3 class="wp-block-heading">The Chat Service<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#the-chat-service"></a></h3>



<p class="wp-block-paragraph">The Blazor app communicates with the API through a simple service:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">ChatApiService</span></div><div class="cm-line">{</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">readonly</span> <span class="tok-variableName">IHttpClientFactory</span> <span class="tok-variableName">_httpClientFactory</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ChatResponse</span><span class="tok-operator">?&gt;</span> <span class="tok-variableName">SendMessageAsync</span>(</div><div class="cm-line">        <span class="tok-typeName">string</span> <span class="tok-variableName">message</span>, </div><div class="cm-line">        <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-variableName">conversationId</span> <span class="tok-operator">=</span> <span class="tok-atom">null</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">client</span> <span class="tok-operator">=</span> <span class="tok-variableName">_httpClientFactory</span>.<span class="tok-variableName">CreateClient</span>(<span class="tok-string">&quot;api&quot;</span>);</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">request</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ChatRequest</span></div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">Message</span> <span class="tok-operator">=</span> <span class="tok-variableName">message</span>,</div><div class="cm-line">            <span class="tok-variableName">ConversationId</span> <span class="tok-operator">=</span> <span class="tok-variableName">conversationId</span></div><div class="cm-line">        };</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">response</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">client</span>.<span class="tok-variableName">PostAsJsonAsync</span>(<span class="tok-string">&quot;/chat&quot;</span>, <span class="tok-variableName">request</span>);</div><div class="cm-line">        <span class="tok-variableName">response</span>.<span class="tok-variableName">EnsureSuccessStatusCode</span>();</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-keyword">await</span> <span class="tok-variableName">response</span>.<span class="tok-variableName">Content</span>.<span class="tok-variableName">ReadFromJsonAsync</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ChatResponse</span><span class="tok-operator">&gt;</span>();</div><div class="cm-line">    }</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">public</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">bool</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">ResetSessionAsync</span>(<span class="tok-typeName">string</span> <span class="tok-variableName">conversationId</span>)</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">client</span> <span class="tok-operator">=</span> <span class="tok-variableName">_httpClientFactory</span>.<span class="tok-variableName">CreateClient</span>(<span class="tok-string">&quot;api&quot;</span>);</div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">response</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">client</span>.<span class="tok-variableName">PostAsync</span>(<span class="tok-variableName">$</span><span class="tok-string">&quot;/reset/{conversationId}&quot;</span>, <span class="tok-atom">null</span>);</div><div class="cm-line">        <span class="tok-keyword">return</span> <span class="tok-variableName">response</span>.<span class="tok-variableName">IsSuccessStatusCode</span>;</div><div class="cm-line">    }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">The Chat Component<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#the-chat-component"></a></h3>



<p class="wp-block-paragraph">Here&#8217;s a simplified view of the Blazor chat interface:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-meta">@page</span> <span class="tok-string">&quot;/chat&quot;</span></div><div class="cm-line"><span class="tok-meta">@inject</span> <span class="tok-variableName">ChatApiService</span> <span class="tok-variableName">ChatApi</span></div><div class="cm-line"><span class="tok-meta">@rendermode</span> <span class="tok-variableName">InteractiveServer</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-operator">&lt;</span><span class="tok-variableName">div</span> <span class="tok-keyword">class</span><span class="tok-operator">=</span><span class="tok-string">&quot;chat-container&quot;</span><span class="tok-operator">&gt;</span></div><div class="cm-line">    <span class="tok-operator">&lt;</span><span class="tok-variableName">div</span> <span class="tok-keyword">class</span><span class="tok-operator">=</span><span class="tok-string">&quot;session-controls&quot;</span><span class="tok-operator">&gt;</span></div><div class="cm-line">        <span class="tok-operator">&lt;</span><span class="tok-keyword">select</span> <span class="tok-meta">@bind</span><span class="tok-operator">=</span><span class="tok-string">&quot;selectedSessionId&quot;</span> <span class="tok-meta">@bind</span>:<span class="tok-variableName">after</span><span class="tok-operator">=</span><span class="tok-string">&quot;OnSessionChanged&quot;</span><span class="tok-operator">&gt;</span></div><div class="cm-line">            <span class="tok-operator">&lt;</span><span class="tok-variableName">option</span> <span class="tok-keyword">value</span><span class="tok-operator">=</span><span class="tok-string">&quot;&quot;</span><span class="tok-operator">&gt;</span><span class="tok-variableName">New</span> <span class="tok-variableName">Conversation</span><span class="tok-operator">&lt;/</span><span class="tok-variableName">option</span><span class="tok-operator">&gt;</span></div><div class="cm-line">            <span class="tok-meta">@foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">session</span> <span class="tok-keyword">in</span> <span class="tok-variableName">sessions</span>)</div><div class="cm-line">            {</div><div class="cm-line">                <span class="tok-operator">&lt;</span><span class="tok-variableName">option</span> <span class="tok-keyword">value</span><span class="tok-operator">=</span><span class="tok-string">&quot;@session&quot;</span><span class="tok-operator">&gt;</span><span class="tok-meta">@session</span><span class="tok-operator">&lt;/</span><span class="tok-variableName">option</span><span class="tok-operator">&gt;</span></div><div class="cm-line">            }</div><div class="cm-line">        <span class="tok-operator">&lt;/</span><span class="tok-keyword">select</span><span class="tok-operator">&gt;</span></div><div class="cm-line">        <span class="tok-operator">&lt;</span><span class="tok-variableName">button</span> <span class="tok-meta">@onclick</span><span class="tok-operator">=</span><span class="tok-string">&quot;ResetSessionAsync&quot;</span><span class="tok-operator">&gt;</span><span class="tok-variableName">&#x1f5d1;</span> <span class="tok-variableName">Reset</span><span class="tok-operator">&lt;/</span><span class="tok-variableName">button</span><span class="tok-operator">&gt;</span></div><div class="cm-line">    <span class="tok-operator">&lt;/</span><span class="tok-variableName">div</span><span class="tok-operator">&gt;</span></div><div class="cm-line"></div><div class="cm-line">    <span class="tok-operator">&lt;</span><span class="tok-variableName">div</span> <span class="tok-keyword">class</span><span class="tok-operator">=</span><span class="tok-string">&quot;chat-messages&quot;</span><span class="tok-operator">&gt;</span></div><div class="cm-line">        <span class="tok-meta">@foreach</span> (<span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">msg</span> <span class="tok-keyword">in</span> <span class="tok-variableName">messages</span>)</div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-operator">&lt;</span><span class="tok-variableName">div</span> <span class="tok-keyword">class</span><span class="tok-operator">=</span><span class="tok-string">&quot;message @msg.Role&quot;</span><span class="tok-operator">&gt;</span></div><div class="cm-line">                <span class="tok-operator">&lt;</span><span class="tok-variableName">span</span><span class="tok-operator">&gt;</span><span class="tok-meta">@</span>(<span class="tok-variableName">msg</span>.<span class="tok-variableName">Role</span> <span class="tok-operator">==</span> <span class="tok-string">&quot;user&quot;</span> <span class="tok-operator">?</span> <span class="tok-string">&quot;&#x1f464;&quot;</span> : <span class="tok-string">&quot;&#x1f916;&quot;</span>)<span class="tok-operator">&lt;/</span><span class="tok-variableName">span</span><span class="tok-operator">&gt;</span></div><div class="cm-line">                <span class="tok-operator">&lt;</span><span class="tok-variableName">span</span><span class="tok-operator">&gt;</span><span class="tok-meta">@msg</span>.<span class="tok-variableName">Text</span><span class="tok-operator">&lt;/</span><span class="tok-variableName">span</span><span class="tok-operator">&gt;</span></div><div class="cm-line">            <span class="tok-operator">&lt;/</span><span class="tok-variableName">div</span><span class="tok-operator">&gt;</span></div><div class="cm-line">        }</div><div class="cm-line">    <span class="tok-operator">&lt;/</span><span class="tok-variableName">div</span><span class="tok-operator">&gt;</span></div><div class="cm-line"></div><div class="cm-line">    <span class="tok-operator">&lt;</span><span class="tok-variableName">textarea</span> <span class="tok-meta">@bind</span><span class="tok-operator">=</span><span class="tok-string">&quot;userMessage&quot;</span> <span class="tok-variableName">placeholder</span><span class="tok-operator">=</span><span class="tok-string">&quot;Type your message...&quot;</span><span class="tok-operator">&gt;&lt;/</span><span class="tok-variableName">textarea</span><span class="tok-operator">&gt;</span></div><div class="cm-line">    <span class="tok-operator">&lt;</span><span class="tok-variableName">button</span> <span class="tok-meta">@onclick</span><span class="tok-operator">=</span><span class="tok-string">&quot;SendMessageAsync&quot;</span><span class="tok-operator">&gt;</span><span class="tok-variableName">Send</span> <span class="tok-variableName">&#x1f4e8;</span><span class="tok-operator">&lt;/</span><span class="tok-variableName">button</span><span class="tok-operator">&gt;</span></div><div class="cm-line"><span class="tok-operator">&lt;/</span><span class="tok-variableName">div</span><span class="tok-operator">&gt;</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-meta">@code</span> {</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-variableName">List</span><span class="tok-operator">&lt;</span><span class="tok-variableName">ChatMessage</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">messages</span> <span class="tok-operator">=</span> <span class="tok-keyword">new</span>();</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-typeName">string</span> <span class="tok-variableName">selectedSessionId</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;&quot;</span>;</div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-typeName">string</span> <span class="tok-variableName">userMessage</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;&quot;</span>;</div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">private</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span> <span class="tok-variableName">SendMessageAsync</span>()</div><div class="cm-line">    {</div><div class="cm-line">        <span class="tok-variableName">messages</span>.<span class="tok-variableName">Add</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">ChatMessage</span> { <span class="tok-variableName">Role</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;user&quot;</span>, <span class="tok-variableName">Text</span> <span class="tok-operator">=</span> <span class="tok-variableName">userMessage</span> });</div><div class="cm-line">        </div><div class="cm-line">        <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">response</span> <span class="tok-operator">=</span> <span class="tok-keyword">await</span> <span class="tok-variableName">ChatApi</span>.<span class="tok-variableName">SendMessageAsync</span>(</div><div class="cm-line">            <span class="tok-variableName">userMessage</span>,</div><div class="cm-line">            <span class="tok-typeName">string</span>.<span class="tok-variableName">IsNullOrEmpty</span>(<span class="tok-variableName">selectedSessionId</span>) <span class="tok-operator">?</span> <span class="tok-atom">null</span> : <span class="tok-variableName">selectedSessionId</span>);</div><div class="cm-line"></div><div class="cm-line">        <span class="tok-keyword">if</span> (<span class="tok-variableName">response</span> <span class="tok-operator">!=</span> <span class="tok-atom">null</span>)</div><div class="cm-line">        {</div><div class="cm-line">            <span class="tok-variableName">selectedSessionId</span> <span class="tok-operator">=</span> <span class="tok-variableName">response</span>.<span class="tok-variableName">ConversationId</span>;  <span class="tok-comment">// Capture the session ID</span></div><div class="cm-line">            <span class="tok-variableName">messages</span>.<span class="tok-variableName">Add</span>(<span class="tok-keyword">new</span> <span class="tok-variableName">ChatMessage</span> { <span class="tok-variableName">Role</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;assistant&quot;</span>, <span class="tok-variableName">Text</span> <span class="tok-operator">=</span> <span class="tok-variableName">response</span>.<span class="tok-variableName">Answer</span> });</div><div class="cm-line">        }</div><div class="cm-line">        </div><div class="cm-line">        <span class="tok-variableName">userMessage</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;&quot;</span>;</div><div class="cm-line">    }</div><div class="cm-line">}</div><div class="cm-line"></div><div class="cm-line"></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The beauty is in the simplicity. The session ID flows through naturally — the Blazor app doesn&#8217;t need to manage complex state synchronization because that&#8217;s Redis&#8217;s job.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/25b6.png" alt="▶" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;Run It Yourself<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#%EF%B8%8F-run-it-yourself"></a></h2>



<p class="wp-block-paragraph">Ready to try it?</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-ttcn"><div class="cm-line"><span class="tok-atom"># Clone the repo</span></div><div class="cm-line"><span class="tok-variableName">git</span> <span class="tok-variableName">clone</span> <span class="tok-variableName">https</span><span class="tok-punctuation">:</span><span class="tok-comment">//github.com/elbruno/maf-agent-sessions-persistence-dotnet</span></div><div class="cm-line"><span class="tok-variableName">cd</span> <span class="tok-variableName">maf</span><span class="tok-operator">-</span><span class="tok-variableName">agent</span><span class="tok-operator">-</span><span class="tok-variableName">sessions</span><span class="tok-operator">-</span><span class="tok-variableName">persistence</span><span class="tok-operator">-</span><span class="tok-variableName">dotnet</span></div><div class="cm-line"></div><div class="cm-line"><span class="tok-atom"># Run with Aspire (starts Redis, Ollama, API, and Web UI)</span></div><div class="cm-line"><span class="tok-variableName">dotnet</span> <span class="tok-variableName">run</span> <span class="tok-operator">--</span><span class="tok-variableName">project</span> <span class="tok-variableName">src</span><span class="tok-operator">/</span><span class="tok-variableName">MafStatefulApi</span><span class="tok-punctuation">.</span><span class="tok-variableName">AppHost</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This spins up:</p>



<ul class="wp-block-list">
<li><strong>Redis</strong>&nbsp;for session storage</li>



<li><strong>Ollama</strong>&nbsp;with a local LLM (llama3.2:1b by default)</li>



<li><strong>API</strong>&nbsp;at port 5256</li>



<li><strong>Blazor Web UI</strong>&nbsp;accessible from the Aspire dashboard</li>
</ul>



<h3 class="wp-block-heading">Test with cURL<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#test-with-curl"></a></h3>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line"># Start a conversation</div><div class="cm-line">curl -X POST http://localhost:5256/chat \</div><div class="cm-line">  -H &quot;Content-Type: application/json&quot; \</div><div class="cm-line">  -d &apos;{&quot;message&quot;: &quot;Hello! My name is Bruno.&quot;}&apos;</div><div class="cm-line"></div><div class="cm-line"># Response: {&quot;conversationId&quot;:&quot;abc123...&quot;,&quot;answer&quot;:&quot;Hello Bruno! ...&quot;}</div><div class="cm-line"></div><div class="cm-line"># Continue the same conversation</div><div class="cm-line">curl -X POST http://localhost:5256/chat \</div><div class="cm-line">  -H &quot;Content-Type: application/json&quot; \</div><div class="cm-line">  -d &apos;{&quot;conversationId&quot;: &quot;abc123...&quot;, &quot;message&quot;: &quot;What is my name?&quot;}&apos;</div><div class="cm-line"></div><div class="cm-line"># Response: {&quot;conversationId&quot;:&quot;abc123...&quot;,&quot;answer&quot;:&quot;Your name is Bruno!&quot;}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">That second request proves memory is working. The agent remembered the name because the session persisted.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Why This Matters<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-why-this-matters"></a></h2>



<p class="wp-block-paragraph">Persisting&nbsp;<code>AgentSession</code>&nbsp;isn&#8217;t just a nice-to-have — it&#8217;s essential for any production AI application:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Without Persistence</th><th>With Persistence</th></tr></thead><tbody><tr><td>Agent forgets everything</td><td>Agent maintains context</td></tr><tr><td>Users repeat themselves</td><td>Conversations flow naturally</td></tr><tr><td>Single instance only</td><td>Horizontally scalable</td></tr><tr><td>Restart = lost state</td><td>Restart = no problem</td></tr><tr><td>Demo quality</td><td>Production quality</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Your AI stops acting like a goldfish and starts acting like… well, an actual assistant.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Resources<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#-resources"></a></h2>



<ul class="wp-block-list">
<li><strong>Repository</strong>:&nbsp;<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet">github.com/elbruno/maf-agent-sessions-persistence-dotnet</a></li>



<li><strong>Microsoft Agent Framework</strong>:&nbsp;<a href="https://learn.microsoft.com/agent-framework/">learn.microsoft.com/agent-framework</a></li>



<li><strong>.NET Aspire</strong>:&nbsp;<a href="https://learn.microsoft.com/dotnet/aspire/">learn.microsoft.com/dotnet/aspire</a></li>



<li><strong>Ollama</strong>:&nbsp;<a href="https://ollama.ai/">ollama.ai</a></li>
</ul>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3ac.png" alt="🎬" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong>Video walkthrough coming soon!</strong>&nbsp;Subscribe to&nbsp;<a href="https://youtube.com/elbruno">youtube.com/elbruno</a>&nbsp;to catch it when it drops.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Wrapping Up<a href="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/blob/main/docs/maf-agent-sessions-persistence-blog.md#wrapping-up"></a></h2>



<p class="wp-block-paragraph">Building production-ready AI agents isn&#8217;t about the model — it&#8217;s about the&nbsp;<em>plumbing</em>. Session persistence is that plumbing.</p>



<p class="wp-block-paragraph">The pattern is simple:</p>



<ol class="wp-block-list">
<li><strong>Load</strong>&nbsp;the session from storage</li>



<li><strong>Run</strong>&nbsp;the agent with context</li>



<li><strong>Save</strong>&nbsp;the updated session</li>
</ol>



<p class="wp-block-paragraph">MAF makes this straightforward with built-in serialization. Redis makes it scalable. Aspire makes it operationally simple.</p>



<p class="wp-block-paragraph">Give your agents a memory. Your users will thank you.</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/03/%f0%9f%a4%96-never-lose-your-ai-agents-train-of-thought/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37180</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/raw/main/docs/images/scene1-1280x720.png">
			<media:title type="html">Stateless APIs vs Multi-turn Chat — The AI keeps asking "What's your name?" because it has no memory</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/raw/main/docs/images/scene3-1280x720.png">
			<media:title type="html">Agent = Stateless Brain, AgentSession = Memory Backpack — Without persistence, you get goldfish memory</media:title>
		</media:content>

		<media:content medium="image" url="https://github.com/elbruno/maf-agent-sessions-persistence-dotnet/raw/main/docs/images/scene2-1280x720.png">
			<media:title type="html">Architecture Flow — ID → Load Session → Run Agent → Save Session</media:title>
		</media:content>
	</item>
		<item>
		<title>&#127897;️ No Tiene Nombre (NTN 470 a 473): cuando la IA promete, cobra, copia… y se mete en problemas</title>
		<link>https://elbruno.com/2026/02/01/%f0%9f%8e%99%ef%b8%8f-no-tiene-nombre-ntn-470-a-473-cuando-la-ia-promete-cobra-copia-y-se-mete-en-problemas/</link>
					<comments>https://elbruno.com/2026/02/01/%f0%9f%8e%99%ef%b8%8f-no-tiene-nombre-ntn-470-a-473-cuando-la-ia-promete-cobra-copia-y-se-mete-en-problemas/#respond</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Sun, 01 Feb 2026 16:41:34 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[no tiene nombre]]></category>
		<category><![CDATA[ntn]]></category>
		<category><![CDATA[Podcast]]></category>
		<category><![CDATA[Spanish Post]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37177</guid>

					<description><![CDATA[En los episodios NTN 470 a 473, No Tiene Nombre entra en una racha especialmente interesante:las grandes empresas de IA empiezan a mostrar sus cartas, y no todas son tan bonitas como los anuncios. Acá hablamos de estrategia, dinero, ética, piratería y bots que se pasan de listos, siempre con contexto, humor y cero humo. [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">En los episodios <strong>NTN 470 a 473</strong>, <em>No Tiene Nombre</em> entra en una racha especialmente interesante:<br />las grandes empresas de IA empiezan a <strong>mostrar sus cartas</strong>, y no todas son tan bonitas como los anuncios.</p>



<p class="wp-block-paragraph">Acá hablamos de <strong>estrategia, dinero, ética, piratería y bots que se pasan de listos</strong>, siempre con contexto, humor y cero humo.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 470 – Google apuesta contra su propio negocio</h2>



<p class="wp-block-paragraph">Google está empujando fuerte la IA… pero el problema es que <strong>su negocio principal vive de otra cosa</strong>.</p>



<p class="wp-block-paragraph">En este episodio se analiza:</p>



<ul class="wp-block-list">
<li>Cómo la IA generativa pone en riesgo el modelo clásico de búsquedas.</li>



<li>Por qué Google parece estar compitiendo contra sí mismo.</li>



<li>El dilema de innovar sin destruir la gallina de los huevos de oro.</li>
</ul>



<p class="wp-block-paragraph">Un episodio ideal para entender que <strong>no todas las decisiones de IA son puramente técnicas</strong>: muchas son defensivas.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br /><a href="https://www.ivoox.com/ntn-470-google-apuesta-contra-su-audios-mp3_rf_167333622_1.html">https://www.ivoox.com/ntn-470-google-apuesta-contra-su-audios-mp3_rf_167333622_1.html</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4b8.png" alt="💸" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 471 – Anthropic trabaja, OpenAI cobra</h2>



<p class="wp-block-paragraph">Uno de esos títulos que ya dice mucho.</p>



<p class="wp-block-paragraph">En este episodio se compara el enfoque de:</p>



<ul class="wp-block-list">
<li><strong>Anthropic</strong>, empujando fuerte herramientas de trabajo real y agentes.</li>



<li><strong>OpenAI</strong>, ajustando precios, planes y modelos de monetización.</li>
</ul>



<p class="wp-block-paragraph">La charla gira alrededor de:</p>



<ul class="wp-block-list">
<li>Quién está apostando a adopción.</li>



<li>Quién está apostando a rentabilidad.</li>



<li>Y qué significa eso para empresas, devs y usuarios.</li>
</ul>



<p class="wp-block-paragraph">Spoiler: <strong>la IA ya no es gratis, ni pretende serlo</strong>.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br /><a href="https://www.ivoox.com/ntn-471-anthropic-trabaja-openai-cobra-audios-mp3_rf_167403023_1.html">https://www.ivoox.com/ntn-471-anthropic-trabaja-openai-cobra-audios-mp3_rf_167403023_1.html</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2696.png" alt="⚖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 472 – IA, ética, piratería y demandas</h2>



<p class="wp-block-paragraph">Este episodio se mete en el terreno incómodo:<br />cuando la IA deja de ser “wow” y empieza a <strong>pisar líneas legales y éticas</strong>.</p>



<p class="wp-block-paragraph">Se habla de:</p>



<ul class="wp-block-list">
<li>Uso de contenido sin permiso para entrenar modelos.</li>



<li>Demandas por piratería y derechos de autor.</li>



<li>El desfase entre lo que la tecnología permite y lo que la ley alcanza a regular.</li>
</ul>



<p class="wp-block-paragraph">No es un episodio alarmista, pero sí muy claro:<br /><strong>la fiesta tiene reglas, aunque todavía no estén todas escritas</strong>.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br /><a href="https://www.ivoox.com/ntn-472-ia-etica-pirateria-y-audios-mp3_rf_167621960_1.html">https://www.ivoox.com/ntn-472-ia-etica-pirateria-y-audios-mp3_rf_167621960_1.html</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> NTN 473 – ClawdBot, MoltBot y el bot que abrió demasiadas puertas</h2>



<p class="wp-block-paragraph">Cerramos con una historia que parece chiste… pero no lo es.</p>



<p class="wp-block-paragraph">En este episodio se cuenta el caso de <strong>ClawdBot / MoltBot</strong>, un bot que en cuestión de horas:</p>



<ul class="wp-block-list">
<li>Se volvió viral.</li>



<li>Accedió a sistemas que no debía.</li>



<li>Y dejó en evidencia problemas serios de seguridad y control.</li>
</ul>



<p class="wp-block-paragraph">La charla sirve para reflexionar sobre:</p>



<ul class="wp-block-list">
<li>Qué pasa cuando soltamos agentes sin límites claros.</li>



<li>Automatización sin gobernanza.</li>



<li>Y por qué “funciona” no siempre significa “está bien”.</li>
</ul>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f3a7.png" alt="🎧" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br /><a href="https://go.ivoox.com/rf/167676850">https://go.ivoox.com/rf/167676850</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/2615.png" alt="☕" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Cierre</h2>



<p class="wp-block-paragraph">Los episodios <strong>NTN 470 a 473</strong> muestran una realidad cada vez más clara:</p>



<p class="wp-block-paragraph">La IA ya no está en fase de promesa.<br />Está en fase de <strong>decisiones, costos, conflictos y consecuencias</strong>.</p>



<p class="wp-block-paragraph">Y entender eso —sin fanatismo ni miedo— es clave para no comerse el verso… ni el marrón.</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f399.png" alt="🎙" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>No Tiene Nombre</em><br />Todos los episodios disponibles en tu plataforma favorita y en <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><a href="https://notienenombre.com?utm_source=chatgpt.com">https://notienenombre.com</a></strong></p>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/02/01/%f0%9f%8e%99%ef%b8%8f-no-tiene-nombre-ntn-470-a-473-cuando-la-ia-promete-cobra-copia-y-se-mete-en-problemas/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37177</post-id>
		<media:thumbnail url="https://elbruno.com/wp-content/uploads/2025/08/25-08-23-blog-post-16-9.png"/>
		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2025/08/25-08-23-blog-post-16-9.png">
			<media:title type="html">25 08 23 blog post 16-9</media:title>
		</media:content>

		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>
	</item>
		<item>
		<title>Building an MCP App with C# – A Color Picker Sample</title>
		<link>https://elbruno.com/2026/01/28/building-an-mcp-app-with-c-a-color-picker-sample/</link>
					<comments>https://elbruno.com/2026/01/28/building-an-mcp-app-with-c-a-color-picker-sample/#comments</comments>
		
		<dc:creator><![CDATA[elbruno]]></dc:creator>
		<pubDate>Wed, 28 Jan 2026 14:00:00 +0000</pubDate>
				<category><![CDATA[EnglishPost]]></category>
		<category><![CDATA[Code Sample]]></category>
		<category><![CDATA[English Post]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[MCP Apps]]></category>
		<guid isPermaLink="false">http://elbruno.com/?p=37161</guid>

					<description><![CDATA[Hi! I’ve been spending some airport time playing with MCP Apps, and one question kept coming back: Can we build a real, interactive UI for MCP tools… using just C#? Short answer: yes.Long answer: let’s build one. In this post, we’ll create a Color Picker MCP App using ASP.NET Core + Model Context Protocol, where: [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <em>This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the <img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in C# are 100% mine.</em></p>



<p class="wp-block-paragraph">Hi!</p>



<p class="wp-block-paragraph">I’ve been spending some airport time playing with <strong>MCP Apps</strong>, and one question kept coming back:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>Can we build a real, interactive UI for MCP tools… using just C#?</em></p>
</blockquote>



<p class="wp-block-paragraph">Short answer: <strong>yes</strong>.<br />Long answer: let’s build one.</p>



<p class="wp-block-paragraph">In this post, we’ll create a <strong>Color Picker MCP App</strong> using <strong>ASP.NET Core + Model Context Protocol</strong>, where:</p>



<ul class="wp-block-list">
<li>an MCP <strong>tool</strong> opens a UI</li>



<li>the UI is provided by an MCP <strong>resource</strong></li>



<li>everything runs locally</li>



<li>everything fits in a single <code>Program.cs</code></li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" width="426" height="240" src="https://elbruno.com/wp-content/uploads/2026/01/mcpapp-cs-colorpicker-demo.gif?w=426" alt="" class="wp-image-37175" srcset="https://elbruno.com/wp-content/uploads/2026/01/mcpapp-cs-colorpicker-demo.gif 426w, https://elbruno.com/wp-content/uploads/2026/01/mcpapp-cs-colorpicker-demo.gif?w=150 150w, https://elbruno.com/wp-content/uploads/2026/01/mcpapp-cs-colorpicker-demo.gif?w=300 300w" sizes="(max-width: 426px) 100vw, 426px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">What are MCP Apps (in one minute)</h2>



<p class="wp-block-paragraph">Traditional MCP tools return <strong>text or structured data</strong>.</p>



<p class="wp-block-paragraph"><strong>MCP Apps</strong> extend that idea by allowing tools to return <strong>interactive UIs</strong>, rendered directly by the MCP host (for example, VS Code).</p>



<p class="wp-block-paragraph">Conceptually:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-brainfuck"><div class="cm-line"><span class="tok-comment">Host</span> <span class="tok-comment">(VS</span> <span class="tok-comment">Code)</span></div><div class="cm-line">   <span class="tok-comment">↓</span> <span class="tok-comment">calls</span> <span class="tok-comment">tool</span></div><div class="cm-line"><span class="tok-comment">MCP</span> <span class="tok-comment">Tool</span></div><div class="cm-line">   <span class="tok-comment">↓</span> <span class="tok-comment">declares</span> <span class="tok-comment">UI</span> <span class="tok-comment">metadata</span></div><div class="cm-line"><span class="tok-comment">MCP</span> <span class="tok-comment">Resource</span> <span class="tok-comment">(ui://...)</span></div><div class="cm-line">   <span class="tok-comment">↓</span> <span class="tok-comment">returns</span> <span class="tok-comment">HTML</span></div><div class="cm-line"><span class="tok-comment">Host</span> <span class="tok-comment">renders</span> <span class="tok-comment">UI</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">So instead of “pick a color by typing <code>#3498DB</code>”, we can let users <strong>click, drag, and visually choose</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Prerequisites</h2>



<p class="wp-block-paragraph">You’ll need:</p>



<ul class="wp-block-list">
<li>.NET SDK (tested with recent .NET versions)</li>



<li>VS Code Insiders </li>



<li>Basic familiarity with ASP.NET minimal APIs</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">The architecture of this sample</h2>



<p class="wp-block-paragraph">This app has <strong>three moving parts</strong>, all in one file:</p>



<ol class="wp-block-list">
<li><strong>MCP Server + HTTP transport</strong></li>



<li><strong>An MCP Tool</strong> (<code>ColorPicker</code>)</li>



<li><strong>An MCP Resource</strong> that serves HTML (<code>ui://color-picker/app.html</code>)</li>
</ol>



<p class="wp-block-paragraph">Once you understand how these three connect, MCP Apps feel very natural.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Step 1: Create the MCP server</h2>



<p class="wp-block-paragraph">We start with a minimal ASP.NET Core app and enable MCP:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">builder</span> <span class="tok-operator">=</span> <span class="tok-variableName">WebApplication</span>.<span class="tok-variableName">CreateBuilder</span>(<span class="tok-variableName">args</span>);</div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">WebHost</span>.<span class="tok-variableName">UseUrls</span>(<span class="tok-string">&quot;http://localhost:3001&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-variableName">builder</span>.<span class="tok-variableName">Services</span>.<span class="tok-variableName">AddMcpServer</span>()</div><div class="cm-line">    .<span class="tok-variableName">WithHttpTransport</span>()</div><div class="cm-line">    .<span class="tok-variableName">WithToolsFromAssembly</span>()</div><div class="cm-line">    .<span class="tok-variableName">WithResourcesFromAssembly</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">app</span> <span class="tok-operator">=</span> <span class="tok-variableName">builder</span>.<span class="tok-variableName">Build</span>();</div><div class="cm-line"></div><div class="cm-line"><span class="tok-comment">// MCP endpoint</span></div><div class="cm-line"><span class="tok-variableName">app</span>.<span class="tok-variableName">MapMcp</span>(<span class="tok-string">&quot;/mcp&quot;</span>);</div><div class="cm-line"></div><div class="cm-line"><span class="tok-keyword">await</span> <span class="tok-variableName">app</span>.<span class="tok-variableName">RunAsync</span>();</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Key things to notice:</p>



<ul class="wp-block-list">
<li>The MCP server listens on <code>/mcp</code></li>



<li>Tools and resources are discovered via <strong>attributes</strong></li>



<li>Everything is exposed over HTTP</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Step 2: Define an MCP Tool</h2>



<p class="wp-block-paragraph">This tool opens a color picker UI.</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line">[<span class="tok-variableName">McpServerToolType</span>]</div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">static</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">ColorPickerTools</span></div><div class="cm-line">{</div><div class="cm-line">  [<span class="tok-variableName">McpServerTool</span>]</div><div class="cm-line">  [<span class="tok-variableName">Description</span>(<span class="tok-string">&quot;Open an interactive color picker to select a color visually.&quot;</span>)]</div><div class="cm-line">  [<span class="tok-variableName">McpMeta</span>(<span class="tok-string">&quot;ui&quot;</span>, <span class="tok-variableName">JsonValue</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;&quot;&quot;{ &quot;</span><span class="tok-variableName">resourceUri</span><span class="tok-string">&quot;: &quot;</span><span class="tok-variableName">ui</span>:<span class="tok-comment">//color-picker/app.html&quot; }&quot;&quot;&quot;)]</span></div><div class="cm-line">  <span class="tok-keyword">public</span> <span class="tok-keyword">static</span> <span class="tok-variableName">ColorPickerResult</span> <span class="tok-variableName">ColorPicker</span>(</div><div class="cm-line">      <span class="tok-typeName">string</span><span class="tok-operator">?</span> <span class="tok-variableName">initialColor</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;#3498DB&quot;</span>)</div><div class="cm-line">  {</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> <span class="tok-variableName">ColorPickerResult</span></div><div class="cm-line">    {</div><div class="cm-line">      <span class="tok-variableName">InitialColor</span> <span class="tok-operator">=</span> <span class="tok-variableName">initialColor</span> <span class="tok-operator">??</span> <span class="tok-string">&quot;#3498DB&quot;</span>,</div><div class="cm-line">      <span class="tok-variableName">Message</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Opening color picker UI...&quot;</span></div><div class="cm-line">    };</div><div class="cm-line">  }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<h3 class="wp-block-heading">Why this works</h3>



<p class="wp-block-paragraph">The important line is:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line">[<span class="tok-variableName">McpMeta</span>(<span class="tok-string">&quot;ui&quot;</span>, <span class="tok-variableName">JsonValue</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;&quot;&quot;{ &quot;</span><span class="tok-variableName">resourceUri</span><span class="tok-string">&quot;: &quot;</span><span class="tok-variableName">ui</span>:<span class="tok-comment">//color-picker/app.html&quot; }&quot;&quot;&quot;)]</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">This tells the MCP host:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">“This tool has a UI. You can find it at this resource URI.”</p>
</blockquote>



<p class="wp-block-paragraph">The tool <strong>does not return HTML</strong>.<br />It returns normal data, plus metadata that links it to a UI resource.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Step 3: Provide the UI as an MCP Resource</h2>



<p class="wp-block-paragraph">Now we define the resource that matches the <code>ui://</code> URI.</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line">[<span class="tok-variableName">McpServerResourceType</span>]</div><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">static</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">ColorPickerResources</span></div><div class="cm-line">{</div><div class="cm-line">  [<span class="tok-variableName">McpServerResource</span>(</div><div class="cm-line">      <span class="tok-variableName">UriTemplate</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;ui://color-picker/app.html&quot;</span>,</div><div class="cm-line">      <span class="tok-variableName">MimeType</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;text/html&quot;</span>,</div><div class="cm-line">      <span class="tok-variableName">Title</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;Color Picker UI&quot;</span>)]</div><div class="cm-line">  <span class="tok-keyword">public</span> <span class="tok-keyword">static</span> <span class="tok-keyword">async</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">GetColorPickerUI</span>()</div><div class="cm-line">  {</div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-keyword">await</span> <span class="tok-variableName">ColorPickerHtmlProvider</span>.<span class="tok-variableName">GetHtml</span>();</div><div class="cm-line">  }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Important rule:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">The <code>resourceUri</code> in the tool <strong>must exactly match</strong> the <code>UriTemplate</code> of the resource.</p>
</blockquote>



<p class="wp-block-paragraph">This is how the host resolves the UI.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Step 4: The HTML UI (served from C#)</h2>



<p class="wp-block-paragraph">The UI itself is just HTML + CSS + JavaScript, returned as a string.</p>



<p class="wp-block-paragraph">In this sample, the UI includes:</p>



<ul class="wp-block-list">
<li>A hue slider</li>



<li>A gradient palette</li>



<li>A preview area</li>



<li>Clipboard copy</li>



<li>VS Code theme variables for native look &amp; feel</li>
</ul>



<p class="wp-block-paragraph">All of it lives inside:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-c#"><div class="cm-line"><span class="tok-keyword">public</span> <span class="tok-keyword">static</span> <span class="tok-keyword">class</span> <span class="tok-variableName tok-definition">ColorPickerHtmlProvider</span></div><div class="cm-line">{</div><div class="cm-line">  <span class="tok-keyword">public</span> <span class="tok-keyword">static</span> <span class="tok-typeName">Task</span><span class="tok-operator">&lt;</span><span class="tok-typeName">string</span><span class="tok-operator">&gt;</span> <span class="tok-variableName">GetHtml</span>()</div><div class="cm-line">  {</div><div class="cm-line">    <span class="tok-keyword">var</span> <span class="tok-variableName tok-definition">html</span> <span class="tok-operator">=</span> <span class="tok-string">&quot;&quot;&quot; </span></div><div class="cm-line">      <span class="tok-operator">&lt;!</span><span class="tok-variableName">DOCTYPE</span> <span class="tok-variableName">html</span><span class="tok-operator">&gt;</span></div><div class="cm-line">      <span class="tok-operator">&lt;</span><span class="tok-variableName">html</span><span class="tok-operator">&gt;</span></div><div class="cm-line">        <span class="tok-operator">&lt;!--</span> <span class="tok-variableName">full</span> <span class="tok-variableName">HTML</span> <span class="tok-variableName">omitted</span> <span class="tok-keyword">for</span> <span class="tok-variableName">brevity</span> <span class="tok-operator">--&gt;</span></div><div class="cm-line">      <span class="tok-operator">&lt;/</span><span class="tok-variableName">html</span><span class="tok-operator">&gt;</span></div><div class="cm-line">    <span class="tok-string">&quot;&quot;&quot;;</span></div><div class="cm-line"></div><div class="cm-line">    <span class="tok-keyword">return</span> <span class="tok-typeName">Task</span>.<span class="tok-variableName">FromResult</span>(<span class="tok-variableName">html</span>);</div><div class="cm-line">  }</div><div class="cm-line">}</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Nothing special here — just standard web tech.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Sending data back to the host</h2>



<p class="wp-block-paragraph">When the user clicks <strong>Select Color</strong>, the UI sends the result back:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-javascript"><div class="cm-line"><span class="tok-variableName">window</span><span class="tok-operator">.</span><span class="tok-propertyName">parent</span><span class="tok-operator">.</span><span class="tok-propertyName">postMessage</span><span class="tok-punctuation">(</span></div><div class="cm-line">  <span class="tok-punctuation">{</span> <span class="tok-propertyName tok-definition">type</span><span class="tok-punctuation">:</span> <span class="tok-string">&apos;mcp-app-result&apos;</span><span class="tok-punctuation">,</span> <span class="tok-propertyName tok-definition">color</span><span class="tok-punctuation">:</span> <span class="tok-variableName">currentColor</span> <span class="tok-punctuation">}</span><span class="tok-punctuation">,</span></div><div class="cm-line">  <span class="tok-string">&apos;*&apos;</span></div><div class="cm-line"><span class="tok-punctuation">)</span><span class="tok-punctuation">;</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">The MCP host listens for this message and uses it as the tool’s final selection.</p>



<p class="wp-block-paragraph">This is what makes the UI <strong>interactive</strong>, not just decorative.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Step 5: Run it in VS Code</h2>



<p class="wp-block-paragraph">Start the server:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code><div class="cm-line">dotnet run</div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Then add it to your MCP configuration:</p>


<div class="wp-block-code">
	<div class="cm-editor">
		<div class="cm-scroller">
			
<pre>
<code class="language-json"><div class="cm-line"><span class="tok-punctuation">{</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;type&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;http&quot;</span><span class="tok-punctuation">,</span></div><div class="cm-line">  <span class="tok-propertyName">&quot;url&quot;</span><span class="tok-punctuation">:</span> <span class="tok-string">&quot;http://localhost:3001/mcp&quot;</span></div><div class="cm-line"><span class="tok-punctuation">}</span></div></code></pre>
		</div>
	</div>
</div>


<p class="wp-block-paragraph">Once loaded, you can invoke the <code>ColorPicker</code> tool and get a fully interactive UI instead of plain text.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Troubleshooting tips</h2>



<p class="wp-block-paragraph">If something doesn’t work:</p>



<ul class="wp-block-list">
<li><strong>404 on <code>/mcp</code></strong> → ensure the app is running on port 3001</li>



<li><strong>Tool not found</strong> → check <code>[McpServerToolType]</code> and <code>[McpServerTool]</code></li>



<li><strong>UI doesn’t render</strong> → verify <code>ui://color-picker/app.html</code> matches exactly</li>



<li><strong>Port already in use</strong> → change <code>UseUrls(...)</code></li>
</ul>



<p class="wp-block-paragraph">Also note: not all MCP clients support <strong>MCP Apps UI</strong> yet.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" />



<h2 class="wp-block-heading">Going further</h2>



<p class="wp-block-paragraph">This sample keeps everything in <code>Program.cs</code> on purpose.</p>



<p class="wp-block-paragraph">For real projects, you may want to:</p>



<ul class="wp-block-list">
<li>Move HTML to <code>wwwroot</code></li>



<li>Split tools and resources into separate files</li>



<li>Return richer typed results</li>



<li>Add authentication when exposing the server publicly (especially with ngrok)</li>
</ul>



<h2 class="wp-block-heading">Resources</h2>



<p class="wp-block-paragraph">Want to learn more about MCP Apps? Here are the official resources:</p>



<ul class="wp-block-list">
<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4fa.png" alt="📺" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://www.youtube.com/watch?v=HWmC3T5Wwqw">VS Code MCP Apps Video</a></strong>&nbsp;&#8211; Visual walkthrough of MCP Apps</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4d6.png" alt="📖" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://modelcontextprotocol.github.io/ext-apps/api/">MCP Apps API Documentation</a></strong>&nbsp;&#8211; Technical details on bringing UI to MCP</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/">MCP Apps Announcement Blog</a></strong>&nbsp;&#8211; The official announcement</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://modelcontextprotocol.io/docs/extensions/apps">MCP Apps QuickStart</a></strong>&nbsp;&#8211; Getting started guide</li>



<li><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://github.com/modelcontextprotocol/ext-apps">Official Samples Repository</a></strong>&nbsp;&#8211; Clone and explore the official samples</li>
</ul>



<h2 class="wp-block-heading">Get the Code</h2>



<p class="wp-block-paragraph">The complete source code is available on GitHub:</p>



<p class="wp-block-paragraph"><img src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;<strong><a href="https://github.com/elbruno/mcpapp-colorpicker">github.com/elbruno/mcpapp-colorpicker</a></strong></p>



<p class="wp-block-paragraph">Feel free to fork it, extend it, or use it as a starting point for your own MCP Apps in .NET!</p>



<div class="wp-block-group is-layout-flow wp-block-group-is-layout-flow">
<p class="wp-block-paragraph">Happy coding!</p>



<p class="wp-block-paragraph">Greetings</p>



<p class="wp-block-paragraph">El Bruno</p>



<p class="wp-block-paragraph">More posts in my blog <a rel="noreferrer noopener" href="https://www.elbruno.com" target="_blank">ElBruno.com</a>.</p>



<p class="wp-block-paragraph">More info in <a href="https://beacons.ai/elbruno" target="_blank" rel="noreferrer noopener">https://beacons.ai/elbruno</a></p>



<hr class="wp-block-separator has-css-opacity is-style-wide" />
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://elbruno.com/2026/01/28/building-an-mcp-app-with-c-a-color-picker-sample/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37161</post-id>
		<media:content medium="image" url="https://2.gravatar.com/avatar/261dbbb4696f8ffecc322124e5623f98dba032a99f5adcc99f8c2d6deb3ab2d3?s=96&amp;d=identicon&amp;r=G">
			<media:title type="html">brunocapuano</media:title>
		</media:content>

		<media:content medium="image" url="https://elbruno.com/wp-content/uploads/2026/01/mcpapp-cs-colorpicker-demo.gif?w=426"/>
	</item>
	</channel>
</rss>