<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>svnscha - tutorial</title>
    <subtitle>automating annoying tasks, sharing tips, and embracing less frustration</subtitle>
    <link rel="self" type="application/atom+xml" href="https://svnscha.de/categories/tutorial/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://svnscha.de"/>
    <generator uri="https://astro.build/">Astro</generator>
    <updated>2026-01-04T00:00:00+00:00</updated>
    <id>https://svnscha.de/categories/tutorial/atom.xml</id>
    <entry xml:lang="en">
        <title>AI Agents in .NET: Beyond DevUI - LibreChat Integration</title>
        <published>2026-01-04T00:00:00+00:00</published>
        <updated>2026-01-04T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-agents-dotnet-part-2/"/>
        <id>https://svnscha.de/posts/ai-agents-dotnet-part-2/</id>
        <summary type="html">Exposing .NET agents through OpenAI-compatible endpoints and connecting them to LibreChat.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-agents-dotnet-part-2/">&lt;p&gt;Welcome back to the AI Agents in .NET series. In the &lt;a href=&quot;/posts/ai-agents-dotnet-intro/&quot;&gt;introduction&lt;/a&gt;, I built a basic conversational agent. In &lt;a href=&quot;/posts/ai-agents-dotnet-part-1/&quot;&gt;Part 1&lt;/a&gt;, I added database persistence, embeddings, and semantic search capabilities.&lt;/p&gt;
&lt;p&gt;Today I'm breaking free from the built-in DevUI. Time to escape the developer dungeon.&lt;/p&gt;
&lt;p&gt;Don't get me wrong - DevUI is fantastic for development and debugging.&lt;/p&gt;
&lt;p&gt;This post connects those agents to LibreChat through OpenAI-compatible endpoints.&lt;/p&gt;
&lt;p&gt;The first version failed when a tool-calling agent was used through a downstream client. The solution was a reusable middleware pattern called &lt;code&gt;ToolCallFilterAgent&lt;/code&gt;, which I explain below.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-devui&quot;&gt;The Problem with DevUI&lt;/h2&gt;
&lt;p&gt;DevUI served me well. It let me test agents, inspect tool calls, and debug issues. But it has limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single-purpose&lt;/strong&gt;: It's a development tool, not a production interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No multi-user support&lt;/strong&gt;: One conversation at a time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited customization&lt;/strong&gt;: You get what you get&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For real-world use, I need something more flexible. And rather than building my own chat UI (been there, done that in early 2025, don't want to repeat), let's leverage existing open-source solutions.&lt;/p&gt;
&lt;h2 id=&quot;why-librechat&quot;&gt;Why LibreChat?&lt;/h2&gt;
&lt;p&gt;I mentioned LibreChat in my &lt;a href=&quot;/posts/dgx-spark-hello-word/&quot;&gt;DGX Spark post&lt;/a&gt;, and I really enjoy it. Like, really like it.&lt;/p&gt;
&lt;p&gt;It's:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenAI API compatible&lt;/strong&gt;: Speaks the same language as ChatGPT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-hosted&lt;/strong&gt;: Your data stays yours (take &lt;em&gt;that&lt;/em&gt;, cloud overlords)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature-rich&lt;/strong&gt;: File uploads, conversations, presets, the whole shebang&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beautiful&lt;/strong&gt;: Actually looks like something you'd want to use - not like a developer accidentally vomited JSON onto a webpage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key insight? LibreChat doesn't care what's behind the API. It just needs something that speaks OpenAI's protocol. And guess what the Microsoft Agent Framework already supports?&lt;/p&gt;
&lt;h2 id=&quot;the-plan&quot;&gt;The Plan&lt;/h2&gt;
&lt;p&gt;Here's what I'm building:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    LC[LibreChat] --&amp;gt;|OpenAI API| K[Knowledge API]
    K --&amp;gt;|Agent Framework| A[Agents]
    A --&amp;gt; KA[Knowledge]
    A --&amp;gt; KSA[KnowledgeSearch]
    A --&amp;gt; KTA[KnowledgeTitle]
    KSA --&amp;gt;|Vector Search| PG[(PostgreSQL)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LibreChat talks to our Knowledge API using the standard OpenAI chat completions format. Our API routes requests to our agents based on the &lt;code&gt;model&lt;/code&gt; parameter. From LibreChat's perspective, it's just another OpenAI-compatible endpoint with multiple models to choose from.&lt;/p&gt;
&lt;h2 id=&quot;removing-devui-embracing-swagger&quot;&gt;Removing DevUI, Embracing Swagger&lt;/h2&gt;
&lt;p&gt;First things first - I'm removing the DevUI dependency and redirecting to Swagger for API exploration. DevUI is great for debugging, but for an API-first approach, Swagger makes more sense. Plus, Swagger has that nice &quot;I'm a real API&quot; energy.&lt;/p&gt;
&lt;p&gt;The home page now redirects to Swagger:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/swagger&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean and simple. The API is now focused on being a backend service. Goodbye, training wheels.&lt;/p&gt;
&lt;h2 id=&quot;exposing-openai-compatible-endpoints&quot;&gt;Exposing OpenAI-Compatible Endpoints&lt;/h2&gt;
&lt;p&gt;The Agent Framework provides a beautiful &lt;code&gt;MapOpenAIChatCompletions&lt;/code&gt; method that exposes agents as OpenAI-compatible endpoints. Each agent gets its own endpoint path. It's almost too easy (foreshadowing...).&lt;/p&gt;
&lt;p&gt;I collect agent builders during registration and map them after building the app:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Collect agent builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IHostedAgentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgeDefaults&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ... OpenAI client setup ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Register agents and collect builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddSingleton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ... middleware setup ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Map OpenAI chat completions endpoint for each registered agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;foreach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIChatCompletions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now each agent has its own endpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/Knowledge/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/KnowledgeSearch/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pretty slick, right?&lt;/p&gt;
&lt;h2 id=&quot;setting-up-librechat&quot;&gt;Setting Up LibreChat&lt;/h2&gt;
&lt;p&gt;Before I go further, let me get LibreChat running so I can test this integration. If you don't have LibreChat running yet, here's the quick setup. (If you do, feel free to skip ahead and judge my configuration choices.)&lt;/p&gt;
&lt;h3 id=&quot;docker-compose&quot;&gt;Docker Compose&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/librechat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/librechat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://github.com/danny-avila/LibreChat.git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .env.example&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .env&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-librechat-configuration&quot;&gt;The LibreChat Configuration&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;librechat.yaml&lt;/code&gt; and point LibreChat at the API:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.2.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;endpoints&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  custom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      apiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;not-used-but-required&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      baseURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://host.docker.internal:5000/v1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      models&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleConvo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      summarize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      forcePrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      modelDisplayLabel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      iconURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://svnscha.de/svnscha.webp&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;docker-compose-override&quot;&gt;Docker Compose Override&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;docker-compose.override.yml&lt;/code&gt; to mount the config:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  api&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;bind&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        source&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;./librechat.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        target&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/app/librechat.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    extra_hosts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;host.docker.internal:host-gateway&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;extra_hosts&lt;/code&gt; line is crucial - it lets containers reach the host machine where the Knowledge API runs.&lt;/p&gt;
&lt;h3 id=&quot;fire-it-up&quot;&gt;Fire It Up&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; up&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Navigate to &lt;code&gt;http://localhost:3080&lt;/code&gt;, create an account (it's local, don't worry), and you should see &quot;Agent Framework&quot; in the endpoint list.&lt;/p&gt;
&lt;p&gt;But wait - if you select &quot;Knowledge&quot; and send a message, LibreChat hits &lt;code&gt;/v1/chat/completions&lt;/code&gt; with &lt;code&gt;&quot;model&quot;: &quot;Knowledge&quot;&lt;/code&gt;. My per-agent endpoints live at &lt;code&gt;/Knowledge/v1/chat/completions&lt;/code&gt; instead. Houston, we have a routing problem.&lt;/p&gt;
&lt;h2 id=&quot;the-model-routing-middleware&quot;&gt;The Model Routing Middleware&lt;/h2&gt;
&lt;p&gt;LibreChat (and most OpenAI clients) expect a single endpoint at &lt;code&gt;/v1/chat/completions&lt;/code&gt; where the &lt;code&gt;model&lt;/code&gt; parameter determines the underlying model but in this scenario it should determines routing:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;messages&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [{&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;What's on your mind?&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I need middleware that reads the &lt;code&gt;model&lt;/code&gt; from the request body and rewrites the path to the agent-specific endpoint. Nothing fancy, just some good old path mangling:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Rewrite /v1/chat/completions to /{model}/v1/chat/completions based on request body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Equals&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StringComparison&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;EnableBuffering&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        using&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; StreamReader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;leaveOpen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ReadToEndAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Position&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            try&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;JsonDocument&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RootElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryGetProperty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;JsonException&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RequestServices&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ILogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Program&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Chat completions request missing required 'model' field&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCodes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Status400BadRequest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ContentType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;The 'model' field is required&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;invalid_request_error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    param&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;missing_required_parameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;$&quot;/&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enable buffering&lt;/strong&gt;: I need to read the body to extract &lt;code&gt;model&lt;/code&gt;, then reset it for the actual handler&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path rewriting&lt;/strong&gt;: Transform &lt;code&gt;/v1/chat/completions&lt;/code&gt; → &lt;code&gt;/{model}/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: Return a proper OpenAI-style error if &lt;code&gt;model&lt;/code&gt; is missing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now when LibreChat sends a request, it gets routed to the right agent automatically. Back to LibreChat - select &quot;Knowledge&quot; from the dropdown, send a message, and... it works! Streaming responses, proper formatting, everything.&lt;/p&gt;
&lt;p&gt;At this point, I thought I was done. Celebrated with a coffee. Time to test &lt;code&gt;KnowledgeSearch&lt;/code&gt;...&lt;/p&gt;
&lt;h2 id=&quot;the-tool-call-disaster-and-how-i-fixed-it&quot;&gt;The Tool Call Disaster (And How I Fixed It)&lt;/h2&gt;
&lt;p&gt;I switched to the &lt;code&gt;KnowledgeSearch&lt;/code&gt; model in LibreChat, asked it to search for something, and... nothing. The request just hung. Then cancelled. No error message. No response. Just the cold, judgmental silence of broken software.&lt;/p&gt;
&lt;p&gt;I dug through LibreChat's source code, traced the request flow, added logging everywhere. Console.WriteLine debugging like it's 2005.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: We should get to Observability very soon...&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;My &lt;code&gt;KnowledgeSearch&lt;/code&gt; agent uses tools - specifically the &lt;code&gt;SearchConversationHistory&lt;/code&gt; function. When the agent executes a tool, the response stream includes &lt;code&gt;FunctionCallContent&lt;/code&gt; and &lt;code&gt;FunctionResultContent&lt;/code&gt; alongside the text. This is how the Agent Framework communicates &quot;I'm calling a tool&quot; and &quot;here's what the tool returned.&quot;&lt;/p&gt;
&lt;p&gt;LibreChat receives the stream and interprets the function call as something it should execute. That fails because the function exists in the backend rather than in LibreChat, so LibreChat cancels the request.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → &quot;I'll search for that&quot; → FunctionCallContent{SearchConversationHistory}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → [executes tool internally]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → FunctionResultContent{results...}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → &quot;Based on the search, here's what I found...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[LibreChat] → Sees FunctionCallContent → &quot;I need to call this function!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[LibreChat] → Can't find function → Cancels request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[User] → &quot;Why isn't anything happening?&quot; 😤&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frustrating part? You can't disable this behavior in LibreChat. It's doing the right thing for its use case - if &lt;em&gt;it&lt;/em&gt; sends a function call to an upstream model, it expects to handle the response. But I'm the upstream model, and I've already handled my own tool calls. We're both right, and that's the most annoying kind of bug.&lt;/p&gt;
&lt;h3 id=&quot;the-solution&quot;&gt;The Solution&lt;/h3&gt;
&lt;p&gt;Don't send tool call content downstream. Filter it out before it leaves the API. If LibreChat never sees the tool calls, it can't get confused by them. &lt;em&gt;taps forehead&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Meet &lt;code&gt;ToolCallFilterAgent&lt;/code&gt; - a delegating agent that wraps any agent and strips &lt;code&gt;FunctionCallContent&lt;/code&gt; and &lt;code&gt;FunctionResultContent&lt;/code&gt; from responses. It's like a bouncer for your API responses:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// A delegating agent that filters out tool call content from responses.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// This prevents downstream consumers from seeing FunctionCallContent and FunctionResultContent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// that they cannot execute.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; sealed&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ToolCallFilterAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;DelegatingAIAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; ToolCallFilterAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AIAgent&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; innerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) : &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;base&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;innerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) { }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AgentRunResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        IEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentThread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentRunOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; InnerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; IAsyncEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AgentRunResponseUpdate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunStreamingAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        IEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentThread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentRunOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EnumeratorCancellation&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; foreach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; InnerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunStreamingAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            yield&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; IList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Contents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionCallContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionResultContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        )).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AgentRunResponseUpdate&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AgentRunResponseUpdate&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Contents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionCallContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionResultContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now in the &lt;code&gt;AgentFactory&lt;/code&gt;, I wrap &lt;code&gt;KnowledgeSearchAgent&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AIAgent&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; CreateKnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IChatClient&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IServiceProvider&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgentOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        ChatOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Instructions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;KnowledgeSearchSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Tools&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AIFunctionFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SearchConversationHistoryAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SearchConversationHistory&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            ToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ChatToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Auto&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Wrap with filter to prevent downstream consumers from seeing tool calls they can't execute&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ToolCallFilterAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent still uses tools internally, but clients only see the final text response. Clean and transparent. What happens in the backend stays in the backend.&lt;/p&gt;
&lt;p&gt;Back to LibreChat - &lt;code&gt;KnowledgeSearch&lt;/code&gt; now works! The agent searches, finds results, and responds - all without LibreChat ever knowing tools were involved. It's like magic, except it's just careful filtering.&lt;/p&gt;
&lt;h2 id=&quot;title-generation-with-knowledgetitleagent&quot;&gt;Title Generation with KnowledgeTitleAgent&lt;/h2&gt;
&lt;p&gt;LibreChat has a feature called &lt;code&gt;titleConvo&lt;/code&gt; - it automatically generates titles for conversations using the AI. But my main agents have tools and complex system prompts that are overkill for simple title generation. It's like using a flamethrower to light a candle.&lt;/p&gt;
&lt;p&gt;The solution: a dedicated title agent. It's intentionally simple - no tools, no embeddings, just a focused system prompt:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Simple agent for generating conversation titles.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// No tools, no embeddings - just a basic helpful assistant.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Designed for use with LibreChat's title generation feature.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; KnowledgeTitleAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; TitleSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;@&quot;You are a helpful assistant that generates concise, descriptive titles for conversations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;When given conversation content, create a brief title (3-7 words) that captures the main topic or purpose.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Be specific and informative. Avoid generic titles like 'Chat' or 'Conversation'.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgent&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Create&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IChatClient&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IServiceProvider&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgentOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            ChatOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                Instructions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;TitleSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register it alongside the other agents:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeTitleAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now update &lt;code&gt;librechat.yaml&lt;/code&gt; to use the title agent:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.2.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;endpoints&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  custom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      apiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;not-used-but-required&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      baseURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://host.docker.internal:5000/v1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      models&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleConvo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      summarize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      forcePrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      modelDisplayLabel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      iconURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://svnscha.de/svnscha.webp&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now LibreChat uses &lt;code&gt;KnowledgeTitle&lt;/code&gt; specifically for generating conversation titles, while &lt;code&gt;Knowledge&lt;/code&gt; and &lt;code&gt;KnowledgeSearch&lt;/code&gt; handle the actual conversations.&lt;/p&gt;
&lt;h2 id=&quot;the-complete-programcs&quot;&gt;The Complete Program.cs&lt;/h2&gt;
&lt;p&gt;Here's what &lt;code&gt;Program.cs&lt;/code&gt; looks like after all these changes:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ClientModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Hosting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WebApplication&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Collect agent builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IHostedAgentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgeDefaults&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;No API key configured. Set Knowledge:ApiKey in user secrets or environment.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClientOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Endpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ApiKeyCredential&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;gpt-4.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AsIChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Configure embedding service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; embeddingClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetEmbeddingClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;text-embedding-3-small&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AsIEmbeddingGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddEmbeddingService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;embeddingClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Register agents and collect builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddSingleton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeTitleAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Register background service for embedding processing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddHostedService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EmbeddingBackgroundService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIResponses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIConversations&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Rewrite /v1/chat/completions to /{model}/v1/chat/completions based on request body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Equals&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StringComparison&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;EnableBuffering&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        using&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; StreamReader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;leaveOpen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ReadToEndAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Position&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            try&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;JsonDocument&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RootElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryGetProperty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;JsonException&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) { }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RequestServices&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ILogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Program&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Chat completions request missing required 'model' field&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCodes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Status400BadRequest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ContentType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;The 'model' field is required&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;invalid_request_error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    param&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;missing_required_parameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;$&quot;/&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UseRouting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgePipeline&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Map OpenAI chat completions endpoint for each registered agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;foreach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIChatCompletions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/swagger&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogStartupComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. No CORS configuration needed (LibreChat makes server-side requests). No custom request/response models. No manual streaming code. The framework does the heavy lifting - I just wire it up. Sometimes the best code is the code you don't have to write.&lt;/p&gt;
&lt;p&gt;The key insights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Each agent gets its own endpoint&lt;/strong&gt; via &lt;code&gt;MapOpenAIChatCompletions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inline middleware handles routing&lt;/strong&gt; from &lt;code&gt;/v1/chat/completions&lt;/code&gt; based on &lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ToolCallFilterAgent hides internal tool execution&lt;/strong&gt; from downstream clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KnowledgeTitleAgent handles title generation&lt;/strong&gt; without tool complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;librechat-test&quot;&gt;LibreChat Test&lt;/h3&gt;
&lt;p&gt;Open LibreChat, select &quot;Agent Framework&quot; as the endpoint, choose a model from the dropdown (&lt;code&gt;Knowledge&lt;/code&gt;, &lt;code&gt;KnowledgeSearch&lt;/code&gt;, or &lt;code&gt;KnowledgeTitle&lt;/code&gt;), and start chatting. You should see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Streaming responses (that satisfying typing effect)&lt;/li&gt;
&lt;li&gt;Tool calls working transparently (KnowledgeSearch uses tools, but you only see the results)&lt;/li&gt;
&lt;li&gt;Automatic title generation (thanks to KnowledgeTitle)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2026-01-04-agent-filter-toolcalls.png&quot; alt=&quot;Image&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h2&gt;
&lt;p&gt;Let me step back and look at what I've built (and maybe pat myself on the back a little):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart TB
    subgraph Clients
        LC[LibreChat]
        API[Direct API Calls]
    end

    subgraph &quot;Knowledge API&quot;
        MR[Model Router Middleware]
        E1[&quot;/Knowledge/v1/chat/completions&quot;]
        E2[&quot;/KnowledgeSearch/v1/chat/completions&quot;]
        E3[&quot;/KnowledgeTitle/v1/chat/completions&quot;]
    end

    subgraph Agents
        KA[Knowledge Agent]
        KSA[KnowledgeSearch Agent]
        KTA[KnowledgeTitle Agent]
        TCF[ToolCallFilterAgent]
    end

    subgraph Storage
        PG[(PostgreSQL)]
    end

    LC --&amp;gt;|&quot;/v1/chat/completions&quot;| MR
    API --&amp;gt; MR
    MR --&amp;gt;|&quot;model: Knowledge&quot;| E1
    MR --&amp;gt;|&quot;model: KnowledgeSearch&quot;| E2
    MR --&amp;gt;|&quot;model: KnowledgeTitle&quot;| E3
    E1 --&amp;gt; KA
    E2 --&amp;gt; TCF
    TCF --&amp;gt; KSA
    E3 --&amp;gt; KTA
    KSA --&amp;gt;|Vector Search| PG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The flow is clean:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request comes in to &lt;code&gt;/v1/chat/completions&lt;/code&gt; with &lt;code&gt;&quot;model&quot;: &quot;KnowledgeSearch&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Middleware rewrites path to &lt;code&gt;/KnowledgeSearch/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Framework routes to the agent, which is wrapped in &lt;code&gt;ToolCallFilterAgent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Agent executes tools internally, filter strips tool content from response&lt;/li&gt;
&lt;li&gt;Client sees clean text output&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Multiple clients, multiple agents, one simple routing pattern. It's almost... elegant?&lt;/p&gt;
&lt;p&gt;Swagger is still available at &lt;code&gt;/swagger&lt;/code&gt; for API exploration and testing.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I've taken the agents from a development-only DevUI to something that can serve real users through a proper chat interface. And I did it with surprisingly little code - plus one hard-won debugging lesson that I'm still a bit salty about.&lt;/p&gt;
&lt;p&gt;The main points are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;MapOpenAIChatCompletions&lt;/code&gt; does the heavy lifting&lt;/strong&gt; - one line per agent, full OpenAI compatibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Each agent gets its own endpoint&lt;/strong&gt; - clean separation, easy to test directly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple inline middleware handles routing&lt;/strong&gt; - LibreChat sends to &lt;code&gt;/v1/chat/completions&lt;/code&gt;, I rewrite to &lt;code&gt;/{model}/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool-calling agents need filtering&lt;/strong&gt; - the &lt;code&gt;ToolCallFilterAgent&lt;/code&gt; pattern is essential when exposing agents to downstream clients that don't understand your internal tool calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dedicated agents for specific tasks&lt;/strong&gt; - &lt;code&gt;KnowledgeTitleAgent&lt;/code&gt; for titles, &lt;code&gt;KnowledgeSearch&lt;/code&gt; for search, &lt;code&gt;Knowledge&lt;/code&gt; for general chat&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;I've got a solid foundation now - persistence, embeddings, semantic search, a proper UI, and automatic title generation. But there's more to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observability&lt;/strong&gt;: OpenTelemetry, Jaeger, understanding what's happening at scale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Loop&lt;/strong&gt;: Exploring the magic behind automated AI Agents&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: Proper API key validation for production deployments (because &quot;meh, whatever&quot; isn't a security strategy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But first, I'm letting this settle. Play with LibreChat, see how your agents perform with real conversations, and notice what's missing. The best features come from actual use - not from staring at code and imagining what users might want.&lt;/p&gt;
&lt;p&gt;See you in the next post. 🚀&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The companion repository has been updated with the &lt;code&gt;part/02-connect-librechat&lt;/code&gt; branch containing all the code from this post. &lt;a href=&quot;https://github.com/svnscha/knowledge/tree/part/02-connect-librechat&quot;&gt;Check it out on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>DGX Spark: Hello World</title>
        <published>2025-12-31T00:00:00+00:00</published>
        <updated>2025-12-31T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/dgx-spark-hello-word/"/>
        <id>https://svnscha.de/posts/dgx-spark-hello-word/</id>
        <summary type="html">My first experiments with NVIDIA's DGX Spark, including local models with Ollama, LibreChat, and ComfyUI.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/dgx-spark-hello-word/">&lt;h2 id=&quot;what-better-way-to-end-2025&quot;&gt;What Better Way to End 2025?&lt;/h2&gt;
&lt;p&gt;So here's how I'm closing out the year: playing with an NVIDIA DGX Spark. Yes, &lt;em&gt;that&lt;/em&gt; DGX Spark - the &quot;personal AI supercomputer&quot; that makes my wallet cry but my inner tech nerd do a happy dance.&lt;/p&gt;
&lt;p&gt;I have already spent some time testing LibreChat and ComfyUI on it. The courses at &lt;a href=&quot;https://stable-diffusion-art.com/&quot;&gt;stable-diffusion-art.com&lt;/a&gt; also helped me get started with image-generation workflows.&lt;/p&gt;
&lt;p&gt;And what better way to ring in the new year than with 128GB of unified memory and a Blackwell GPU sitting on my desk? I mean, some people do fireworks. I do neural networks.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Yes, I'm aware that's possibly the nerdiest New Year's joke ever.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let me walk you through my first-day adventures getting this beautiful beast up and running. Fair warning: there may have been moments of childlike excitement, some creative problem-solving (read: frantically searching for a USB-C keyboard), and an unhealthy amount of &quot;let's try this and see what happens.&quot;&lt;/p&gt;
&lt;h2 id=&quot;the-unboxing&quot;&gt;The Unboxing&lt;/h2&gt;
&lt;p&gt;There's something special about unboxing new hardware. But unboxing a &lt;em&gt;DGX&lt;/em&gt;? That hits different.&lt;/p&gt;
&lt;p&gt;The packaging screams premium - NVIDIA clearly understands that when you're dropping serious money on a personal AI supercomputer, presentation matters. Opening the box felt like a tech ritual. Inside, nestled in protective foam, sat this compact powerhouse that somehow packs a Blackwell GPU and 128GB of unified memory into a form factor that actually fits on a desk.&lt;/p&gt;
&lt;div class=&quot;image-gallery&quot;&gt;
  &lt;img src=&quot;/screenshots/2025-12-22-dgx-spark-unbox-1.jpg&quot; alt=&quot;DGX Spark unboxing - first look&quot;&gt;
  &lt;img src=&quot;/screenshots/2025-12-22-dgx-spark-unbox-2.jpg&quot; alt=&quot;DGX Spark - the unit&quot;&gt;
  &lt;img src=&quot;/screenshots/2025-12-22-dgx-spark-unbox-3.jpg&quot; alt=&quot;DGX Spark - ready to go&quot;&gt;
  &lt;img src=&quot;/screenshots/25-12-22-dgx-spark-unbox-4.jpg&quot; alt=&quot;DGX Spark - booting&quot;&gt;
&lt;/div&gt;
&lt;p&gt;The unit itself is surprisingly compact. I expected something massive and loud, but NVIDIA engineered this thing to be almost civilized. Almost. We'll see how that holds up once I start pushing it with larger models. Everything was included - power cables, documentation, and that unmistakable feeling of &lt;em&gt;&quot;I'm about to have way too much fun with this.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Alright, enough admiring the hardware. Time to make it do things.&lt;/p&gt;
&lt;h2 id=&quot;the-setup-experience&quot;&gt;The Setup Experience&lt;/h2&gt;
&lt;p&gt;I went with the local installation method - straightforward enough. Well, mostly. Here's a fun discovery: do you own a keyboard with USB Type-C? No? Neither did I. Fortunately, a laptop docking station saved the day. Crisis averted.&lt;/p&gt;
&lt;p&gt;Once the initial setup was complete, first things first - let's give this machine a proper identity:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; hostnamectl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; set-hostname&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; spark-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; reboot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why &lt;code&gt;spark-1&lt;/code&gt;? Because who knows if there'll be a &lt;code&gt;spark-2&lt;/code&gt; someday. Better to be prepared. Future me will thank present me for this foresight. Or curse me for the temptation.&lt;/p&gt;
&lt;h2 id=&quot;installing-ollama&quot;&gt;Installing Ollama&lt;/h2&gt;
&lt;p&gt;After updating the system, I started with something familiar. &lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; has become my go-to for local LLM deployment - it's absurdly simple to set up:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://ollama.com/install.sh&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I know what you're thinking - &lt;em&gt;&quot;But what about vLLM? What about llama.cpp? What about those fancy self-compiled versions optimized for Blackwell's SM120 architecture?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yes, I've seen those posts. Yes, I'll explore them. But that's a future adventure. For now, let's start with the classics and make sure everything actually works before we go down the optimization rabbit hole.&lt;/p&gt;
&lt;p&gt;With Ollama ready, time to pull a model:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ollama&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; pull&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; gpt-oss:20b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Could I run the massive 120B parameter models? Absolutely - that's kind of the whole point of having this hardware. But let's walk before we run. Download a smaller model to verify everything works, then let the big ones download overnight while I sleep. Strategy.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-librechat&quot;&gt;Setting Up LibreChat&lt;/h2&gt;
&lt;p&gt;Running models from the CLI is fine for testing, but I wanted a web interface. I previously used &lt;a href=&quot;/posts/self-hosted-llm/&quot;&gt;Open WebUI&lt;/a&gt; for my self-hosted LLM setup; this time I chose &lt;a href=&quot;https://github.com/danny-avila/LibreChat&quot;&gt;LibreChat&lt;/a&gt;, an open-source chat UI that supports local models.&lt;/p&gt;
&lt;p&gt;Docker was pre-installed on the DGX Spark (nice touch, NVIDIA). Though my user wasn't in the docker group - but I'll assume you can figure that one out.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/workspaces&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/workspaces/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://github.com/danny-avila/LibreChat.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; LibreChat/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .env.example&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .env&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After some experimentation, I settled on these configuration changes:&lt;/p&gt;
&lt;h3 id=&quot;environment-configuration&quot;&gt;Environment Configuration&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;.env&lt;/code&gt;, the key change:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ENDPOINTS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;custom&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;docker-compose-override&quot;&gt;Docker Compose Override&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;docker-compose.override.yml&lt;/code&gt; for your custom settings, and a &lt;code&gt;librechat.yaml&lt;/code&gt; for the LibreChat-specific configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.override.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  api&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;bind&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        source&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;./librechat.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        target&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/app/librechat.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;librechat.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.2.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;endpoints&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  custom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Ollama&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      apiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ollama&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      baseURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://host.docker.internal:11434/v1/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      models&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          &quot;gpt-oss:20b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleConvo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;current_model&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      summarize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      summaryModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;current_model&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      forcePrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      modelDisplayLabel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Ollama&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's... almost it.&lt;/p&gt;
&lt;h2 id=&quot;the-reverse-proxy-dance&quot;&gt;The Reverse Proxy Dance&lt;/h2&gt;
&lt;p&gt;Here's where it gets slightly more involved. LibreChat enforces secure cookies by default - great for security, annoying when you're hitting F5 every five seconds during development and don't want to re-authenticate each time.&lt;/p&gt;
&lt;p&gt;The solution? Set up a reverse proxy with HTTPS. I use &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; in my home infrastructure because life's too short to manage SSL certificates manually.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;chat.int.domain.tld {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @lan remote_ip 172.16.29.0/24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy @lan http://172.16.2.123:3080&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        templates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        respond &quot;Access denied, {{.RemoteIP}}&quot; 403&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration does a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only allows access from my local network (the &lt;code&gt;172.16.29.0/24&lt;/code&gt; range)&lt;/li&gt;
&lt;li&gt;Proxies requests to the DGX Spark running LibreChat on port 3080&lt;/li&gt;
&lt;li&gt;Returns a 403 for anyone trying to access from outside&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's not always open from outside but when it is (to refresh certificates) this is a great and easy way to deal with this.&lt;/p&gt;
&lt;p&gt;I've also configured a custom DNS zone in my router to make &lt;code&gt;chat.int.domain.tld&lt;/code&gt; resolve correctly, but that's infrastructure stuff for another post.&lt;/p&gt;
&lt;h2 id=&quot;first-impressions&quot;&gt;First Impressions&lt;/h2&gt;
&lt;p&gt;And just like that - we're live. Quick setup, everything working, ready to chat with local models running on actual Blackwell hardware. The response times are snappy, the interface is clean, and I can already tell this is going to be a fun playground.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Now that the basics are running, it's time to explore what this hardware can actually do. On my list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Document Understanding&lt;/strong&gt;: Feed it files and see how well it comprehends them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Voice integration&lt;/strong&gt;: Testing spoken conversations with local models&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image generation with ComfyUI&lt;/strong&gt;: Exploring image-generation workflows on the Blackwell GPU. The &lt;a href=&quot;https://stable-diffusion-art.com/&quot;&gt;stable-diffusion-art.com&lt;/a&gt; courses have been a useful starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are just the fundamentals to get comfortable with the platform. But honestly? Between LibreChat's slick interface for chatting with local models and ComfyUI's node-based wizardry for image generation, I've already had more fun than should be legal.&lt;/p&gt;
&lt;p&gt;It was a good way to end 2025: experimenting with new hardware and learning what it can do.&lt;/p&gt;
&lt;p&gt;Here's to closing out the year with new adventures, and to many more in 2026! 🎉&lt;/p&gt;
&lt;p&gt;I will share more results as I continue testing the DGX Spark.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>AI Agents in .NET: Building Agentic RAG</title>
        <published>2025-12-28T00:00:00+00:00</published>
        <updated>2025-12-28T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-agents-dotnet-part-1/"/>
        <id>https://svnscha.de/posts/ai-agents-dotnet-part-1/</id>
        <summary type="html">Taking our agent from 'hello world' to useful - with database persistence, embeddings, vector search, and tools that let it reason over documentation and conversation history.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-agents-dotnet-part-1/">&lt;p&gt;Welcome back. In the &lt;a href=&quot;/posts/ai-agents-dotnet-intro/&quot;&gt;first post&lt;/a&gt;, we set up a basic conversational agent. It worked. It responded. It was... fine.&lt;/p&gt;
&lt;p&gt;But let's be honest - an agent that can only chat is just a very expensive echo chamber. Today we're giving our agent actual knowledge. It will remember past conversations, search through its own message history, and - most importantly - decide &lt;em&gt;when&lt;/em&gt; to use that knowledge.&lt;/p&gt;
&lt;p&gt;This is going to be a long one. Grab coffee. Let's build the next piece of the puzzle.&lt;/p&gt;
&lt;h2 id=&quot;what-were-building&quot;&gt;What We're Building&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2025-12-28-knowledge-search.png&quot; alt=&quot;Knowledge Search Agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;By the end of this post, we'll have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A PostgreSQL database&lt;/strong&gt; for persisting messages and embeddings&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A second agent&lt;/strong&gt; (KnowledgeSearch) that searches conversation history&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool calling&lt;/strong&gt; so our main agent can invoke KnowledgeSearch when needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autonomous decision-making&lt;/strong&gt; about when to retrieve information vs. just respond&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is the key difference between classic RAG and &lt;em&gt;agentic&lt;/em&gt; RAG. We're not blindly stuffing context into every prompt. The agent chooses when retrieval helps.&lt;/p&gt;
&lt;h2 id=&quot;a-quick-word-on-rag&quot;&gt;A Quick Word on RAG&lt;/h2&gt;
&lt;p&gt;RAG - Retrieval-Augmented Generation - has been the go-to pattern for giving LLMs access to external knowledge. The classic approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User asks a question&lt;/li&gt;
&lt;li&gt;System searches a knowledge base&lt;/li&gt;
&lt;li&gt;Retrieved documents get stuffed into the prompt&lt;/li&gt;
&lt;li&gt;LLM generates a response using that context&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It works. But it's also... dumb? Every query triggers retrieval, whether needed or not. Ask &quot;What's the capital of France?&quot; and you're still embedding the question, searching vectors, retrieving documents. Wasteful.&lt;/p&gt;
&lt;p&gt;Agentic RAG flips this. The agent has &lt;em&gt;tools&lt;/em&gt; for retrieval and decides when to use them. Simple questions get simple answers. Complex questions trigger the agent to go digging. Much more elegant.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt;: There's a whole universe of RAG variations out there - hybrid RAG combining vector search with full-text search (FTS), reranking strategies, query expansion, and more. If you go down the rabbit hole, you'll find dozens of ways to improve retrieval quality. We're intentionally skipping all of that here to focus on the &lt;em&gt;agentic&lt;/em&gt; part - how an agent decides when and how to retrieve. Once you grok that, feel free to explore the retrieval optimizations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enough theory. Let's write some code.&lt;/p&gt;
&lt;h2 id=&quot;database-foundation&quot;&gt;Database Foundation&lt;/h2&gt;
&lt;p&gt;Our agent needs a place to store things - messages, embeddings, knowledge. PostgreSQL with pgvector gives us a solid foundation.&lt;/p&gt;
&lt;h3 id=&quot;the-schema&quot;&gt;The Schema&lt;/h3&gt;
&lt;p&gt;We're keeping it simple: messages for chat history, embeddings for semantic search.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {
  &quot;theme&quot;: &quot;dark&quot;,
  &quot;themeVariables&quot;: {
    &quot;fontFamily&quot;: &quot;Jetbrains Mono&quot;,
  },
  &quot;themeCSS&quot;: [
    &quot;.er.relationshipLine { stroke: #858585; }&quot;,
    &quot;.er.relationshipLabel { fill: #bbbbbb; }&quot;,
    &quot;.er.relationshipLabelBox { fill: transparent; }&quot;,
    &quot;.er.entityBox { fill: #252526; stroke: #303031; }&quot;,
    &quot;[id^=entity-messages] .er.entityBox { fill: #1e3a5f; stroke: #5abae0; }&quot;,
    &quot;[id^=entity-embeddings] .er.entityBox { fill: #451a03; stroke: #f59e0b; }&quot;
    ]
}}%%
erDiagram
    messages ||--o| embeddings : &quot;has&quot;
    messages {
        uuid id PK
        uuid conversation_id
        uuid embedding_id FK
        varchar role
        varchar author_name
        text content
        bigint sequence_number
        timestamp created_at
    }
    embeddings {
        uuid id PK
        varchar source_type
        uuid source_id
        text content
        vector vector_1536
        timestamp created_at
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;conversation_id&lt;/strong&gt;: Groups messages by conversation/session - indexed for fast retrieval&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;role&lt;/strong&gt;: Maps to &lt;code&gt;ChatRole.Value&lt;/code&gt; from Microsoft.Extensions.AI (&lt;code&gt;user&lt;/code&gt;, &lt;code&gt;assistant&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;tool&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sequence_number&lt;/strong&gt;: Auto-assigned by a database trigger - ensures correct ordering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;source_type&lt;/strong&gt;: Polymorphic pattern - can reference &lt;code&gt;Message&lt;/code&gt; or any future entity (e.g., &lt;code&gt;DocumentChunk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vector_1536&lt;/strong&gt;: 1536-dimension embedding from OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-dbcontext&quot;&gt;The DbContext&lt;/h3&gt;
&lt;p&gt;Entity Framework Core handles the mapping. The key thing we need is pgvector support:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeDbContext.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modelBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;HasPostgresExtension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;vector&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This single line enables the &lt;code&gt;vector&lt;/code&gt; data type in PostgreSQL. Without it, EF Core won't know what to do with our embedding vectors.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;Embedding&lt;/code&gt; entity, we tell EF Core the exact column type:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeDbContext.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Property&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;HasColumnType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;vector(1536)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rest is standard EF Core configuration - indexes on &lt;code&gt;ConversationId&lt;/code&gt; (our primary query pattern), composite index on &lt;code&gt;ConversationId + SequenceNumber&lt;/code&gt; for ordered retrieval, and &lt;code&gt;SourceType + SourceId&lt;/code&gt; on embeddings for fast lookups.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't skip the indexes.&lt;/strong&gt; Without them, you're doing full table scans.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;wiring-it-up&quot;&gt;Wiring It Up&lt;/h3&gt;
&lt;p&gt;Database registration goes into our extension method. The important choice: &lt;code&gt;AddDbContextFactory&lt;/code&gt; instead of &lt;code&gt;AddDbContext&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ServiceCollectionExtensions.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddDbContextFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeDbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UseNpgsql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;connectionString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;npgsqlOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        npgsqlOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UseVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Enable pgvector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why the factory? Our message store and agents live outside the normal HTTP request lifecycle. The factory pattern lets each operation spin up its own short-lived context.&lt;/p&gt;
&lt;h3 id=&quot;migration-time&quot;&gt;Migration Time&lt;/h3&gt;
&lt;p&gt;EF Core migrations version our schema:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Knowledge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Create the migration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ef&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; migrations&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; InitialCreate&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --project&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ../Knowledge.Shared&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --output-dir&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Apply it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ef&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; database&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --project&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ../Knowledge.Shared&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also add a trigger migration for auto-incrementing sequence numbers:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 20251224000001_AddCustomTriggers.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;migrationBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Sql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    CREATE OR REPLACE FUNCTION assign_message_sequence_number()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    RETURNS TRIGGER AS $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    BEGIN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        SELECT COALESCE(MAX(sequence_number), 0) + 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        INTO NEW.sequence_number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        FROM messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        WHERE conversation_id = NEW.conversation_id;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        RETURN NEW;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    END;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    $$ LANGUAGE plpgsql;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;migrationBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Sql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    CREATE TRIGGER trg_messages_sequence_number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    BEFORE INSERT ON messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    FOR EACH ROW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    WHEN (NEW.sequence_number = 0 OR NEW.sequence_number IS NULL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    EXECUTE FUNCTION assign_message_sequence_number();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This assigns sequence numbers per conversation. The &lt;code&gt;WHERE conversation_id = NEW.conversation_id&lt;/code&gt; ensures each conversation has its own sequence. For high-concurrency production use, consider using a proper sequence/identity instead of &lt;code&gt;MAX()&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-conversation-workaround&quot;&gt;The Conversation Workaround&lt;/h3&gt;
&lt;p&gt;Before we get to the message store, there's a wrinkle. The framework's DevUI doesn't pass &lt;code&gt;AgentThread&lt;/code&gt; information to custom &lt;code&gt;ChatMessageStore&lt;/code&gt; implementations. No thread ID means no way to group messages by conversation using the framework's intended abstractions.&lt;/p&gt;
&lt;p&gt;We could give up on persistence entirely. Or we could be pragmatic.&lt;/p&gt;
&lt;p&gt;The workaround is dead simple: generate a conversation ID once at startup.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ConversationWorkaround.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ConversationWorkaround&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; CurrentConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Guid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One GUID, initialized once when the application starts, shared for the lifetime of the process. Every message gets tagged with that ID. Restart the app? New ID, new conversation.&lt;/p&gt;
&lt;p&gt;We add a corresponding &lt;code&gt;ConversationId&lt;/code&gt; column to our &lt;code&gt;Message&lt;/code&gt; entity - just a &lt;code&gt;Guid&lt;/code&gt; property that gets persisted alongside the role, content, and sequence number.&lt;/p&gt;
&lt;h3 id=&quot;the-message-store&quot;&gt;The Message Store&lt;/h3&gt;
&lt;p&gt;With our workaround in place, the &lt;code&gt;ChatMessageStore&lt;/code&gt; implementation becomes straightforward. The framework calls &lt;code&gt;AddMessagesAsync&lt;/code&gt; after each exchange, and &lt;code&gt;GetMessagesAsync&lt;/code&gt; when loading history.&lt;/p&gt;
&lt;p&gt;The key insight: we use &lt;code&gt;IDbContextFactory&amp;lt;KnowledgeDbContext&amp;gt;&lt;/code&gt; instead of injecting a &lt;code&gt;DbContext&lt;/code&gt; directly. Why? The store lives outside the normal HTTP request lifecycle. The factory pattern lets each operation spin up its own short-lived context - no connection leaks, no stale entity tracking.&lt;/p&gt;
&lt;p&gt;When adding messages, we tag each one with &lt;code&gt;ConversationWorkaround.CurrentConversationId&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeChatMessageStore.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Guid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryParse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;MessageId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) ? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Guid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ConversationWorkaround&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;CurrentConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    AuthorName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AuthorName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ?? &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    SequenceNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Trigger auto-assigns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    CreatedAt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;CreatedAt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;UtcDateTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ?? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;UtcNow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When retrieving, we filter by that same ID:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeChatMessageStore.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;conversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;OrderBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SequenceNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps each app instance isolated. Messages from yesterday's debugging session don't pollute today's conversation.&lt;/p&gt;
&lt;h3 id=&quot;why-this-workaround&quot;&gt;Why This Workaround?&lt;/h3&gt;
&lt;p&gt;The ideal solution would use the framework's &lt;code&gt;AgentThread&lt;/code&gt; abstraction - each thread gets its own conversation, the UI manages thread creation, everything Just Works™. But DevUI doesn't pass thread information to custom stores yet.&lt;/p&gt;
&lt;p&gt;Good news: our &lt;code&gt;ChatMessageStore&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; get called. Messages persist. The agent loads context on startup. The workaround gives us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-instance conversations&lt;/strong&gt;: Each app restart starts fresh&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent context&lt;/strong&gt;: Within a session, the agent remembers everything&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future-proof schema&lt;/strong&gt;: When DevUI supports threads, we swap &lt;code&gt;ConversationWorkaround.CurrentConversationId&lt;/code&gt; for the real thread ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We've opened &lt;a href=&quot;https://github.com/microsoft/agent-framework/issues/3000&quot;&gt;issue #3000&lt;/a&gt; to track proper thread support. For now, this gets the job done.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;embeddings&quot;&gt;Embeddings&lt;/h2&gt;
&lt;p&gt;Before building the KnowledgeSearch agent, it helps to understand embeddings, which are the basis of semantic search. If you already know how they work, you can skip this section.&lt;/p&gt;
&lt;p&gt;This section covers the theory, but we're not just hand-waving. As we go, we'll be building toward our second agent that can search through conversation history and return relevant context.&lt;/p&gt;
&lt;h3 id=&quot;the-big-reveal&quot;&gt;The Big Reveal&lt;/h3&gt;
&lt;p&gt;An embedding is just a list of numbers. That's it. That's the tweet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    A[&quot;🐱 cat&quot;] --&amp;gt; B[&quot;[0.021, -0.034, 0.089, 0.012, ..., -0.045]&quot;]
    style A fill:#064e3b,stroke:#10b981,color:#fff
    style B fill:#252526,stroke:#303031,color:#bbbbbb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you feed text into an embedding model, it spits out a vector - 1536 floating-point numbers for OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt;. These numbers encode the &lt;em&gt;meaning&lt;/em&gt; of the text. Not the letters, not the spelling - the actual semantic content.&lt;/p&gt;
&lt;p&gt;The magic? Similar meanings produce similar numbers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    A[&quot;🐱 cat&quot;] --&amp;gt; V1[&quot;[0.021, -0.034, 0.089, ...]&quot;]
    B[&quot;🐈 kitten&quot;] --&amp;gt; V2[&quot;[0.019, -0.031, 0.092, ...]&quot;]
    C[&quot;🗳️ democracy&quot;] --&amp;gt; V3[&quot;[-0.067, 0.142, -0.023, ...]&quot;]

    style A fill:#064e3b,stroke:#10b981,color:#fff
    style B fill:#064e3b,stroke:#10b981,color:#fff
    style C fill:#451a03,stroke:#f59e0b,color:#fff
    style V1 fill:#252526,stroke:#303031,color:#bbbbbb
    style V2 fill:#252526,stroke:#303031,color:#bbbbbb
    style V3 fill:#252526,stroke:#303031,color:#bbbbbb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See how &quot;cat&quot; and &quot;kitten&quot; have similar-ish numbers, while &quot;democracy&quot; is completely different? That's semantic similarity, encoded as math.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The &quot;magic&quot; demystified&lt;/strong&gt;: This is the whole secret behind AI &quot;understanding&quot; - it's just numbers. There's no mystical intelligence, no consciousness pondering the nature of cats. Just insanely clever linear algebra operating in 1536-dimensional space. Once you see it, you can't unsee it. Beautiful, really.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;where-things-live-in-vector-space&quot;&gt;Where Things Live in Vector Space&lt;/h3&gt;
&lt;p&gt;Imagine plotting these vectors in space. The real vectors have 1,536 dimensions, but a three-dimensional sketch is enough to show the idea of clusters and similarity scores.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {
  &quot;theme&quot;: &quot;dark&quot;
}}%%
flowchart LR
    subgraph Pets[&quot; &quot;]
        A[cat]
        B[kitten]
    end
    subgraph Civics[&quot; &quot;]
        D[democracy]
        E[parliament]
    end

    A ---|&quot;0.94&quot;| B
    A -.-|&quot;0.31&quot;| D
    D ---|&quot;0.89&quot;| E

    style A fill:#064e3b,stroke:#10b981,color:#fff
    style B fill:#064e3b,stroke:#10b981,color:#fff
    style D fill:#451a03,stroke:#f59e0b,color:#fff
    style E fill:#451a03,stroke:#f59e0b,color:#fff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar concepts cluster together. Words about pets huddle in one corner, political terms in another. In the real 1536-dimensional space, these relationships become incredibly nuanced - the model captures categories, analogies, context, even vibes. The scores on the edges are cosine similarities: higher numbers mean those meanings live closer together.&lt;/p&gt;
&lt;p&gt;This is why semantic search works. Ask &quot;how do I create an agent?&quot; and the system finds documents about &quot;instantiating agents&quot; and &quot;agent initialization&quot; - different words, same neighborhood in vector space.&lt;/p&gt;
&lt;h3 id=&quot;how-close-are-two-vectors&quot;&gt;How &quot;Close&quot; Are Two Vectors?&lt;/h3&gt;
&lt;p&gt;We use &lt;strong&gt;cosine similarity&lt;/strong&gt; - basically measuring the angle between two arrows in space.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Similarity = 1&lt;/strong&gt;: Identical meaning (same direction)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Similarity = 0&lt;/strong&gt;: Unrelated (perpendicular)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Similarity = -1&lt;/strong&gt;: Opposite meaning (opposite direction)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don't need to understand the math. Just know: &lt;strong&gt;higher number = more similar meaning&lt;/strong&gt;. When we search our knowledge base, we're finding the vectors that point in roughly the same direction as the query.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    subgraph Query
        Q[&quot;How do I build an agent?&quot;]
    end
    subgraph Results[&quot;Top Matches (by similarity)&quot;]
        R1[&quot;0.94 - Creating your first agent&quot;]
        R2[&quot;0.91 - Agent initialization guide&quot;]
        R3[&quot;0.87 - Setting up agent tools&quot;]
        R4[&quot;0.34 - PostgreSQL connection strings&quot;]
    end
    Q --&amp;gt; R1
    Q --&amp;gt; R2
    Q --&amp;gt; R3
    Q -.-&amp;gt; R4

    style Q fill:#1e3a5f,stroke:#5abae0,color:#fff
    style R1 fill:#064e3b,stroke:#10b981,color:#fff
    style R2 fill:#064e3b,stroke:#10b981,color:#fff
    style R3 fill:#064e3b,stroke:#10b981,color:#fff
    style R4 fill:#451a03,stroke:#f59e0b,color:#fff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The PostgreSQL doc isn't &lt;em&gt;wrong&lt;/em&gt;, it's just pointing in a completely different direction. Low similarity score, doesn't make the cut.&lt;/p&gt;
&lt;h3 id=&quot;the-model-well-use&quot;&gt;The Model We'll Use&lt;/h3&gt;
&lt;p&gt;OpenAI offers several embedding models:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-embedding-3-small&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1536&lt;/td&gt;
&lt;td&gt;Most applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-embedding-3-large&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3072&lt;/td&gt;
&lt;td&gt;When you need more context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-embedding-ada-002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1536&lt;/td&gt;
&lt;td&gt;Legacy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We'll use &lt;code&gt;text-embedding-3-small&lt;/code&gt;. It's cheap, fast, and good enough for most use cases. The larger model is more accurate but costs more - classic tradeoff. Start small, upgrade if you need to.&lt;/p&gt;
&lt;p&gt;Now the question becomes: where do we store 1536 floating-point numbers per piece of text, and how do we search them efficiently?&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;pgvector-setup&quot;&gt;pgvector Setup&lt;/h2&gt;
&lt;p&gt;PostgreSQL with pgvector gives us a proper vector database without leaving our existing stack. No separate Pinecone or Weaviate instance to manage.&lt;/p&gt;
&lt;h3 id=&quot;enabling-the-extension&quot;&gt;Enabling the Extension&lt;/h3&gt;
&lt;p&gt;First, we need pgvector enabled in our database. Connect to PostgreSQL and run:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; EXTENSION &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;IF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This installs the vector data type and operators. You only need to do this once per database. If you're using my dev container template, this is already handled in the database initialization scripts.&lt;/p&gt;
&lt;p&gt;Verify it worked:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; pg_extension &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; extname = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'vector'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;-- Should return one row showing the vector extension&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-embedding-entity&quot;&gt;The Embedding Entity&lt;/h3&gt;
&lt;p&gt;We want a generic approach - store embeddings for any content type (messages, documents, whatever). The key property:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Embedding.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Vector&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;!;  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// pgvector's native type&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use pgvector's native &lt;code&gt;Vector&lt;/code&gt; type instead of &lt;code&gt;float[]&lt;/code&gt;. The &lt;code&gt;Pgvector.EntityFrameworkCore&lt;/code&gt; package handles the mapping between C# and PostgreSQL.&lt;/p&gt;
&lt;p&gt;The entity also tracks &lt;em&gt;what&lt;/em&gt; was embedded via &lt;code&gt;SourceType&lt;/code&gt; + &lt;code&gt;SourceId&lt;/code&gt; - a poor man's polymorphic association. Not elegant, but flexible. We can add new source types (documents, code snippets) without schema changes.&lt;/p&gt;
&lt;p&gt;We also store the original &lt;code&gt;Content&lt;/code&gt; alongside the vector. Why? When we retrieve matches, we need the actual text to show. And if embeddings get stale, we can re-generate from the stored content.&lt;/p&gt;
&lt;h3 id=&quot;ef-core-configuration&quot;&gt;EF Core Configuration&lt;/h3&gt;
&lt;p&gt;We already covered the key line in Section 1 - &lt;code&gt;HasColumnType(&quot;vector(1536)&quot;)&lt;/code&gt;. The dimension must match your embedding model. OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt; produces 1536 dimensions, so that's what we use.&lt;/p&gt;
&lt;h3 id=&quot;indexing-for-speed&quot;&gt;Indexing for Speed&lt;/h3&gt;
&lt;p&gt;Without an index, similarity search scans every row - fine for 1,000 embeddings, catastrophic for 1,000,000. HNSW (Hierarchical Navigable Small World) gives us logarithmic search time.&lt;/p&gt;
&lt;p&gt;We create the index via a migration (we include this in our &lt;code&gt;AddCustomTriggers&lt;/code&gt; migration):&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 20251224000001_AddCustomTriggers.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;migrationBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Sql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    CREATE INDEX IF NOT EXISTS ix_embeddings_vector_cosine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    ON embeddings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    USING hnsw (vector vector_cosine_ops);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking this down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hnsw&lt;/code&gt;&lt;/strong&gt;: The index algorithm - builds a graph structure for fast nearest neighbor search&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;vector_cosine_ops&lt;/code&gt;&lt;/strong&gt;: Use cosine distance (1 - cosine similarity) as the metric&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Creation time&lt;/strong&gt;: ~1-2 seconds per 10,000 vectors on my laptop&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tradeoff: HNSW uses more memory and takes longer to build, but queries are dramatically faster. For a RAG system, this is always worth it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;embedding-messages&quot;&gt;Embedding Messages&lt;/h2&gt;
&lt;p&gt;Time to give our agent some actual knowledge. Instead of indexing external documentation, we're embedding our own conversation history - every message becomes searchable.&lt;/p&gt;
&lt;h3 id=&quot;why-messages&quot;&gt;Why Messages?&lt;/h3&gt;
&lt;p&gt;We need &lt;em&gt;something&lt;/em&gt; to embed, and chat messages are already there - no external datasets, no document ingestion pipelines, no extra setup. It's demo content that generates itself as you use the agent.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important distinction&lt;/strong&gt;: This is &lt;em&gt;not&lt;/em&gt; the same as &lt;code&gt;Microsoft.Agents.AI.Memory.ChatHistoryMemoryProvider&lt;/code&gt;, which provides memorable context during agent invocation (think: short-term working memory for the current conversation). What we're building is long-term semantic search over historical content. Different problems, different solutions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-chunking-problem-or-not&quot;&gt;The Chunking Problem (Or Not)&lt;/h3&gt;
&lt;p&gt;For large documents, chunking is essential - split into smaller pieces, embed each one. But messages are already bite-sized. A typical chat message is well under our embedding model's context limit.&lt;/p&gt;
&lt;p&gt;We'll embed messages as-is. No chunking needed. If you later want to add document indexing (PDFs, markdown files, whatever), you'd apply chunking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fixed size&lt;/strong&gt;: Every chunk is N tokens. Simple but might split mid-sentence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paragraph-based&lt;/strong&gt;: Split on natural boundaries. Preserves context but uneven sizes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic&lt;/strong&gt;: Use the LLM to identify logical sections. Expensive but smart.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For documents, &lt;strong&gt;fixed size with overlap&lt;/strong&gt; (500 tokens, 50 token overlap) is the standard. But for messages, we skip this entirely.&lt;/p&gt;
&lt;h3 id=&quot;the-embedding-service&quot;&gt;The Embedding Service&lt;/h3&gt;
&lt;p&gt;We wrap &lt;code&gt;IEmbeddingGenerator&amp;lt;string, Embedding&amp;lt;float&amp;gt;&amp;gt;&lt;/code&gt; from Microsoft.Extensions.AI in a simple service. The key conversion:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// EmbeddingService.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; _embeddingGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GenerateAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToArray&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Text goes in, pgvector &lt;code&gt;Vector&lt;/code&gt; comes out. The service handles the conversion from the AI library's float array to pgvector's native type. Nothing fancy - just a thin wrapper to keep the embedding generation consistent across the app.&lt;/p&gt;
&lt;h3 id=&quot;background-processing&quot;&gt;Background Processing&lt;/h3&gt;
&lt;p&gt;We could embed messages synchronously when they're saved, but that blocks the chat response. Instead, a background service picks up unprocessed messages every 10 seconds.&lt;/p&gt;
&lt;p&gt;The trick is using &lt;code&gt;Message.EmbeddingId&lt;/code&gt; as both a foreign key &lt;em&gt;and&lt;/em&gt; a processing flag:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// EmbeddingBackgroundService.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pendingMessages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;EmbeddingId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Not yet embedded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; !&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;OrderBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SequenceNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Take&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each message, we generate the embedding and link it back. Once &lt;code&gt;EmbeddingId&lt;/code&gt; is set, that message won't be picked up again.&lt;/p&gt;
&lt;p&gt;No chunking, no complexity. Message goes in, vector comes out, gets stored. The &lt;code&gt;SourceType = &quot;Message&quot;&lt;/code&gt; links back to the original - we'll use this when searching.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-knowledgesearch-agent&quot;&gt;The KnowledgeSearch Agent&lt;/h2&gt;
&lt;p&gt;Once the embeddings are generated, the KnowledgeSearch agent can use them to search the conversation history by meaning.&lt;/p&gt;
&lt;h3 id=&quot;why-an-agent-instead-of-a-simple-tool&quot;&gt;Why an Agent Instead of a Simple Tool?&lt;/h3&gt;
&lt;p&gt;You could just wire up a tool function. But wrapping it in an agent brings benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clear separation&lt;/strong&gt;: Search logic lives in its own class, testable in isolation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency injection&lt;/strong&gt;: Proper lifetime management for database contexts and services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt;: Later, we can add more specialized agents (DocSearch, WebSearch, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the &quot;agentic&quot; part of agentic RAG - agents with tools, each specialized for their task.&lt;/p&gt;
&lt;h3 id=&quot;the-implementation&quot;&gt;The Implementation&lt;/h3&gt;
&lt;p&gt;The flow is straightforward: embed the query, search pgvector, format results.&lt;/p&gt;
&lt;p&gt;First, we embed the search query using the same model that embedded our messages:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeSearchAgent.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; queryVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; _embeddingService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;EmbedAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The vector search uses pgvector:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeSearchAgent.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Embeddings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SourceType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Embedding&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Distance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CosineDistance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queryVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Distance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;lt;= (&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DefaultMinScore&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// DefaultMinScore = 0.40&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;OrderBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Distance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Take&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DefaultTopK&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// DefaultTopK = 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CosineDistance()&lt;/code&gt;&lt;/strong&gt; returns distance (0 = identical, 2 = opposite), not similarity. Lower is better.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Threshold of &lt;code&gt;(1 - 0.40)&lt;/code&gt;&lt;/strong&gt; means we want at least 40% similarity. Tweak &lt;code&gt;DefaultMinScore&lt;/code&gt; based on your use case.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IDbContextFactory&lt;/code&gt;&lt;/strong&gt; gives us a fresh context per search - the agent is a singleton, but each query gets its own connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, we fetch the actual messages and format them with timestamps. The timestamps matter - they help the LLM understand recency.&lt;/p&gt;
&lt;h3 id=&quot;the-pgvector-query&quot;&gt;The pgvector Query&lt;/h3&gt;
&lt;p&gt;EF Core translates this LINQ query to:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.*, e.vector &amp;lt;=&amp;gt; @queryVector &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; distance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; embeddings e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.source_type = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'Message'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  AND&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.vector &amp;lt;=&amp;gt; @queryVector &amp;lt;= &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  -- 1 - DefaultMinScore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.vector &amp;lt;=&amp;gt; @queryVector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; operator is pgvector's cosine distance. Lower distance = better match. The HNSW index we created earlier makes this logarithmic instead of linear - critical at scale.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;wiring-it-up-1&quot;&gt;Wiring It Up&lt;/h2&gt;
&lt;p&gt;Now we connect everything in &lt;code&gt;AgentFactory&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-agent-factory&quot;&gt;The Agent Factory&lt;/h3&gt;
&lt;p&gt;Wiring the tool is the key step. We use &lt;code&gt;AIFunctionFactory.Create()&lt;/code&gt; to turn our instance method into something the agent can call:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// AgentFactory.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgentOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ChatOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Instructions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;KnowledgeSearchSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Tools&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AIFunctionFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SearchConversationHistoryAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        ToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ChatToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Auto&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;[Description]&lt;/code&gt; attributes on our method and parameters become the tool's schema - the LLM sees them and knows what the tool does and what arguments it needs.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ToolMode = ChatToolMode.Auto&lt;/code&gt; is important. It tells the model to decide when to use tools, rather than always using them or never using them.&lt;/p&gt;
&lt;p&gt;The system prompt is equally crucial. We tell the LLM &lt;em&gt;when&lt;/em&gt; to use the tool:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DECISION FRAMEWORK:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- For general knowledge (math, common facts): Answer directly without tools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- For recall questions ('did we discuss X?'): Use SearchConversationHistory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- When unsure if something was discussed: Search first rather than guessing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without this guidance, models either never use tools or use them for everything. The prompt teaches restraint.&lt;/p&gt;
&lt;h3 id=&quot;registration-in-programcs&quot;&gt;Registration in Program.cs&lt;/h3&gt;
&lt;p&gt;The key registration:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Program.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddSingleton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why singleton? The agent factory resolves from the root provider during startup. This is safe because &lt;code&gt;KnowledgeSearchAgent&lt;/code&gt; only depends on singletons (&lt;code&gt;IEmbeddingService&lt;/code&gt;, &lt;code&gt;ILogger&lt;/code&gt;) and factories (&lt;code&gt;IDbContextFactory&lt;/code&gt;) - no scoped services leaking through.&lt;/p&gt;
&lt;p&gt;The rest follows the same pattern we established: &lt;code&gt;AddAIAgent&lt;/code&gt; with factory delegates, &lt;code&gt;AddHostedService&lt;/code&gt; for the background embedding processor.&lt;/p&gt;
&lt;h3 id=&quot;before-and-after&quot;&gt;Before and After&lt;/h3&gt;
&lt;p&gt;Let's see the difference:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Without RAG:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User: &quot;What did we discuss about database indexes?&quot;
Agent: &quot;I don't have memory of our past conversations...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;With KnowledgeSearch:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User: &quot;What did we discuss about database indexes?&quot;
Agent: &lt;em&gt;calls SearchConversationHistory(&quot;database indexes&quot;)&lt;/em&gt;
Agent: &quot;Based on our earlier conversation, we discussed adding indexes for ConversationId and the composite index for ordered retrieval...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Night and day.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We've covered a lot of ground today, but we're still just scratching the surface.&lt;/p&gt;
&lt;p&gt;We learned how to take abstract concepts like &lt;strong&gt;embeddings&lt;/strong&gt; and store them in an &lt;strong&gt;actual database&lt;/strong&gt; using &lt;code&gt;pgvector&lt;/code&gt;. We saw how to &lt;strong&gt;consume that knowledge&lt;/strong&gt; by building a specialized agent that can perform semantic searches over our own conversation history.&lt;/p&gt;
&lt;p&gt;Along the way, we touched on the importance of &lt;strong&gt;database persistence&lt;/strong&gt;, the nuances of &lt;strong&gt;tool calling&lt;/strong&gt; with &lt;code&gt;AIFunctionFactory&lt;/code&gt;, and how to manage agent lifetimes in a .NET application. We also explored &lt;strong&gt;chunking&lt;/strong&gt; and more advanced &lt;strong&gt;RAG strategies&lt;/strong&gt; like hybrid search and reranking - topics that are definitely worth exploring as you dive deeper into the world of AI.&lt;/p&gt;
&lt;p&gt;The full code is on the &lt;a href=&quot;https://github.com/svnscha/knowledge-private/tree/part/01-agentic-rag&quot;&gt;&lt;code&gt;part/01-agentic-rag&lt;/code&gt;&lt;/a&gt; branch. Clone it, run it, break it, and most importantly, make it your own.&lt;/p&gt;
&lt;p&gt;Microsoft's official examples are also worth reading. The framework implements some features, including search, with higher-level abstractions such as &lt;code&gt;TextSearchProvider&lt;/code&gt;. I used the lower-level approach here to make each step visible.&lt;/p&gt;
&lt;p&gt;Here are some examples worth checking in the &lt;a href=&quot;https://github.com/microsoft/agent-framework&quot;&gt;Microsoft Agent Framework&lt;/a&gt; repository:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/GettingStarted&quot;&gt;Getting Started on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Agent_Step03_UsingFunctionTools/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent_Step06_PersistedConversations/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent_Step07_3rdPartyThreadStorage/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent_Step12_AsFunctionTool/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentWithRAG/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;We now have an agent that can remember and retrieve earlier conversations.&lt;/p&gt;
&lt;p&gt;The next step is the &lt;strong&gt;Agent Loop&lt;/strong&gt;. Instead of a single request and response, the agent can act, observe the result, and adjust its next step until the task is complete. This pattern is the basis for more autonomous coding assistants.&lt;/p&gt;
&lt;p&gt;As these agents become more autonomous, we also need to know exactly what they're thinking. That's why we'll be diving into &lt;strong&gt;Observability&lt;/strong&gt; and tracing so we can peek inside the &quot;black box&quot; and see every tool call, every reasoning step, and every decision the agent makes in real-time.&lt;/p&gt;
&lt;p&gt;We're moving from &quot;chatbots&quot; to &quot;collaborators.&quot; I'll see you in the next one as we start closing the loop. 🚀&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>AI Agents in .NET: Let's Build Something Smart</title>
        <published>2025-12-21T00:00:00+00:00</published>
        <updated>2025-12-21T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-agents-dotnet-intro/"/>
        <id>https://svnscha.de/posts/ai-agents-dotnet-intro/</id>
        <summary type="html">Introducing the Knowledge repository - a hands-on companion for learning how to build AI agents with the Microsoft Agent Framework in C#.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-agents-dotnet-intro/">&lt;p&gt;Welcome to a new blog series about building AI agents in .NET.&lt;/p&gt;
&lt;p&gt;Yes, you read that right - I just said &lt;code&gt;.NET&lt;/code&gt;. Not Python. C#. Curly braces. Strong typing. With drums and trumpets.&lt;/p&gt;
&lt;p&gt;Over the coming posts, we'll explore the &lt;a href=&quot;https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview&quot;&gt;Microsoft Agent Framework&lt;/a&gt;, build actual agentic systems, and - most importantly - have some fun doing it. This first post sets the stage: why I'm doing this, what we're building, and how to get started with the companion repository.&lt;/p&gt;
&lt;p&gt;Here is how to get started.&lt;/p&gt;
&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Honestly? I got a little restless. Every AI agent tutorial out there follows the same well-worn path:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&quot;Build a weather agent!&quot;&lt;/em&gt;  -  Revolutionary stuff. Though I hear windows also exist.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&quot;Spam detection with AI!&quot;&lt;/em&gt;  -  Gmail's been doing this since 2004.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&quot;Customer service chatbot!&quot;&lt;/em&gt;  -  Oh good, we definitely need more of those.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These examples are useful for learning the basics, but I wanted to build something closer to a real application.&lt;/p&gt;
&lt;p&gt;This series is about building agents that are genuinely smart, genuinely fun, or ideally both. No boring examples. No wrapping API calls in agent clothing and calling it intelligent.&lt;/p&gt;
&lt;p&gt;As for the language choice - I've always been a C-family person. Python is lovely for what it does, but give me curly braces and strong typing any day. When Microsoft released the Agent Framework for .NET? Yeah, that got me genuinely excited. First-class AI agents in C#. Native support, proper tooling, the whole package.&lt;/p&gt;
&lt;h2 id=&quot;the-microsoft-agent-framework&quot;&gt;The Microsoft Agent Framework&lt;/h2&gt;
&lt;p&gt;So what exactly are we working with? The framework comes with everything you need to build proper agentic systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native C# support (obviously)&lt;/li&gt;
&lt;li&gt;A beautiful &lt;a href=&quot;https://learn.microsoft.com/en-us/agent-framework/user-guide/devui/?pivots=programming-language-csharp&quot;&gt;DevUI&lt;/a&gt; for testing and debugging your agents - still in preview and missing some Python-side features, but it gets the job done&lt;/li&gt;
&lt;li&gt;Built-in patterns for common agentic scenarios (the fun stuff)&lt;/li&gt;
&lt;li&gt;Integration with the broader .NET ecosystem (finally!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that's what this blog series is all about.&lt;/p&gt;
&lt;h3 id=&quot;about-devui&quot;&gt;About DevUI&lt;/h3&gt;
&lt;p&gt;Speaking of DevUI - if you head over to Microsoft's documentation and switch to the C# tab, you're greeted with this gem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&quot;DevUI documentation for C# is coming soon. Please check back later or refer to the Python documentation for conceptual guidance.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah yes, the classic &quot;refer to Python&quot; move. You know, the language we &lt;em&gt;specifically&lt;/em&gt; chose not to use for this series? The irony isn't lost on me. But hey, I'm not bitter - I'm just impressed they managed to ship a working DevUI before the documentation. That's actually kind of bold.&lt;/p&gt;
&lt;p&gt;The DevUI is a useful web interface for chatting with agents and inspecting their behavior during development. It does not replace proper tracing with tools such as OpenTelemetry and Jaeger, but it works well for quick iteration and debugging. I will cover observability separately in a later post.&lt;/p&gt;
&lt;h2 id=&quot;the-knowledge-repository&quot;&gt;The Knowledge Repository&lt;/h2&gt;
&lt;p&gt;To support this series, I've created a companion repository called &lt;a href=&quot;https://github.com/svnscha/knowledge&quot;&gt;Knowledge&lt;/a&gt;. I briefly considered &lt;em&gt;&quot;AI-Stuff-I-Throw-Together-At-2AM&quot;&lt;/em&gt; but that didn't seem like a good fit. So I thought about it - I'm deepening my own knowledge about AI as I create this series, and hopefully you'll gain some knowledge along the way too. Yeah, let's face it: &lt;em&gt;Knowledge&lt;/em&gt; is simple. Sometimes simple works.&lt;/p&gt;
&lt;p&gt;The repository is structured as a progressive learning path. Each branch builds on the previous one, taking you from zero to building sophisticated agentic systems. Here's what we're working with:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;What You'll Learn&lt;/th&gt;
&lt;th&gt;Codespace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repository &amp;amp; Hello World Agent&lt;/td&gt;
&lt;td&gt;Project setup, DevUI, your first conversational agent&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://codespaces.new/svnscha/knowledge/tree/main&quot;&gt;&lt;img src=&quot;https://github.com/codespaces/badge.svg&quot; alt=&quot;Open in Codespaces&quot;&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;More branches will be added as the series progresses. And honestly? That Codespaces button is kind of magical - click it, grab a coffee, and come back to a fully configured dev environment. No setup, no dependencies, just ready to go. I still find that ridiculously cool.&lt;/p&gt;
&lt;p&gt;Star the repo if you want to follow along!&lt;/p&gt;
&lt;h2 id=&quot;development-environment&quot;&gt;Development Environment&lt;/h2&gt;
&lt;p&gt;You've got two options here - pick whichever fits your workflow.&lt;/p&gt;
&lt;h3 id=&quot;dev-container-setup&quot;&gt;Dev Container Setup&lt;/h3&gt;
&lt;p&gt;First things first: reproducible development environments. The repository includes a &lt;code&gt;.devcontainer&lt;/code&gt; configuration with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.NET 10&lt;/li&gt;
&lt;li&gt;PostgreSQL with pgvector for when we get into embeddings and semantic search&lt;/li&gt;
&lt;li&gt;All the tooling pre-configured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just open the repo in VS Code, click &quot;Reopen in Container,&quot; and you're ready to go. The classic &quot;works on my machine&quot; problem? Solved.&lt;/p&gt;
&lt;p&gt;You can also &lt;a href=&quot;https://codespaces.new/svnscha/knowledge/tree/main&quot;&gt;open it in a Codespace&lt;/a&gt; and work entirely in the browser without a local setup.&lt;/p&gt;
&lt;h3 id=&quot;classic&quot;&gt;Classic&lt;/h3&gt;
&lt;p&gt;And for those of you rolling your eyes at all this container and cloud stuff - fair enough. Install the &lt;a href=&quot;https://dotnet.microsoft.com/download&quot;&gt;.NET 10 SDK&lt;/a&gt;, fire up VS Code or Visual Studio, clone the repo, and just go. Sometimes the old ways are the best ways. That said, you'll want to get PostgreSQL with pgvector set up sooner rather than later - we'll need it for embeddings and semantic search in upcoming posts.&lt;/p&gt;
&lt;h2 id=&quot;whats-on-the-main-branch&quot;&gt;What's on the Main Branch?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2025-12-15-devui-empty.png&quot; alt=&quot;The DevUI in action&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let me walk you through what you'll find when you clone the repo. The &lt;code&gt;main&lt;/code&gt; branch sets up everything you need to hit the ground running with AI agent development.&lt;/p&gt;
&lt;h3 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h3&gt;
&lt;p&gt;The solution is organized into two projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Knowledge&lt;/strong&gt; - The main web application that hosts our agents and the DevUI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge.Shared&lt;/strong&gt; - Shared configuration, extensions, and utilities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This separation keeps things clean as we add more complexity in future posts.&lt;/p&gt;
&lt;h3 id=&quot;configuration-done-right&quot;&gt;Configuration Done Right&lt;/h3&gt;
&lt;p&gt;One thing I'm particular about is handling configuration properly. In &lt;code&gt;Knowledge.Shared&lt;/code&gt;, we have a &lt;code&gt;KnowledgeSettings&lt;/code&gt; class that gives us strongly-typed access to our config values.&lt;/p&gt;
&lt;p&gt;The important bits are these two properties:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ApiKey&lt;/code&gt; is where your OpenAI (or compatible) API key goes. &lt;code&gt;ApiEndpoint&lt;/code&gt; is optional - leave it empty and we'll hit OpenAI's default endpoint, or set it to point at Azure OpenAI, a local model, whatever you need.&lt;/p&gt;
&lt;p&gt;Now, you definitely don't want to hardcode API keys in your source code. That's how keys end up on GitHub and suddenly you're funding a stranger's LLM experiments. Instead, we use &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets&quot;&gt;.NET User Secrets&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Knowledge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; user-secrets&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Knowledge:ApiKey&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;your-api-key-here&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your key stays on your machine, outside of source control. Sleep well at night.&lt;/p&gt;
&lt;h3 id=&quot;wiring-up-the-agent&quot;&gt;Wiring Up the Agent&lt;/h3&gt;
&lt;p&gt;The following sections walk through &lt;code&gt;Program.cs&lt;/code&gt; piece by piece.&lt;/p&gt;
&lt;p&gt;First, the imports - nothing too surprising here:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ClientModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;DevUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Hosting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We're pulling in the Agent Framework (&lt;code&gt;Microsoft.Agents.AI.*&lt;/code&gt;), Microsoft's AI abstractions (&lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt;), and the official OpenAI client. Standard .NET web app stuff otherwise.&lt;/p&gt;
&lt;p&gt;Next, we create our builder and call into our configuration extension:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WebApplication&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgeDefaults&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ConfigureKnowledgeDefaults&lt;/code&gt; is a helper method in our shared project. It handles loading &lt;code&gt;appsettings.json&lt;/code&gt;, binding it to our &lt;code&gt;KnowledgeSettings&lt;/code&gt; type, and then gives us a callback where we can access both the settings and a logger. This keeps &lt;code&gt;Program.cs&lt;/code&gt; clean while still giving us full control.&lt;/p&gt;
&lt;p&gt;Inside that callback, we set up OpenAI. First, a sanity check:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;No API key configured. Set Knowledge:ApiKey in user secrets.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Better to warn loudly than fail silently.&lt;/p&gt;
&lt;p&gt;Then we create the OpenAI client options. If a custom endpoint is configured, we use it:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClientOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Endpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what lets you swap between OpenAI, Azure OpenAI, or even a local model running through something like Ollama with an OpenAI-compatible API.&lt;/p&gt;
&lt;p&gt;Now we create the actual client and register it with dependency injection:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ApiKeyCredential&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;gpt-4.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AsIChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GetChatClient(&quot;gpt-4.1&quot;)&lt;/code&gt; gets us a chat client for that specific model. &lt;code&gt;.AsIChatClient()&lt;/code&gt; wraps it in Microsoft's &lt;code&gt;IChatClient&lt;/code&gt; abstraction, which is what the Agent Framework expects. Using this abstraction means we could swap out OpenAI for any other provider without changing our agent code.&lt;/p&gt;
&lt;p&gt;And finally - the actual agent registration:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;You are a helpful agent named Knowledge.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. One line. We give it a name and a system prompt, and the framework handles the rest. We'll make this more sophisticated in future posts, but for a &quot;hello world&quot; agent, this is all you need.&lt;/p&gt;
&lt;h3 id=&quot;finishing-the-pipeline&quot;&gt;Finishing the Pipeline&lt;/h3&gt;
&lt;p&gt;After our configuration callback, we register a few more services:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIResponses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIConversations&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These set up the Agent Framework's handlers for OpenAI's responses and conversations API patterns.&lt;/p&gt;
&lt;p&gt;Then we build the app and configure the HTTP pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgePipeline&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ConfigureKnowledgePipeline&lt;/code&gt; is another shared helper - it sets up logging, error handling, and other middleware that we'll want across all our experiments.&lt;/p&gt;
&lt;p&gt;Finally, we map our endpoints:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIResponses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIConversations&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapDevUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/devui/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;MapOpenAIResponses()&lt;/code&gt; and &lt;code&gt;MapOpenAIConversations()&lt;/code&gt; expose endpoints that follow OpenAI's API patterns. &lt;code&gt;MapDevUI()&lt;/code&gt; enables the Agent Framework's built-in developer interface.&lt;/p&gt;
&lt;p&gt;That last &lt;code&gt;MapGet&lt;/code&gt; just redirects the root URL to the DevUI. Because when you're developing agents, that's where you want to be.&lt;/p&gt;
&lt;h3 id=&quot;running-the-application&quot;&gt;Running the Application&lt;/h3&gt;
&lt;p&gt;Once configured, getting started is simple:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --project&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Knowledge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Navigate to &lt;code&gt;http://localhost:5000/devui&lt;/code&gt; and you'll see the DevUI - a beautiful interface for interacting with your agents, inspecting their reasoning, and debugging when things inevitably go sideways.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We've covered a lot of ground in this introductory post. You now have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A companion repository with a reproducible dev container setup&lt;/li&gt;
&lt;li&gt;A working &quot;hello world&quot; agent using the Microsoft Agent Framework&lt;/li&gt;
&lt;li&gt;Proper configuration with user secrets (no API keys in source control!)&lt;/li&gt;
&lt;li&gt;The DevUI for testing and debugging your agents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The foundation is solid. Now comes the fun part.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;We've got the foundation in place - a working agent, DevUI, proper configuration. But let's be real: a &quot;hello world&quot; agent isn't going to impress anyone, least of all ourselves.&lt;/p&gt;
&lt;p&gt;Next, I will add tools so the agent can perform actions, explore multi-agent scenarios, and use PostgreSQL with pgvector for embeddings and semantic search. A later post will also add proper observability.&lt;/p&gt;
&lt;p&gt;But first things first. Step by step.&lt;/p&gt;
&lt;p&gt;If you're a .NET developer who's been watching the AI agent space from the sidelines, wondering when C# would get some love - welcome. If you're just someone who's bored of the same old tutorials and wants to see something different - also welcome.&lt;/p&gt;
&lt;p&gt;Clone the repo, star it if you're feeling generous, and let's build something that's actually worth building.&lt;/p&gt;
&lt;p&gt;See you in the next post. 🚀&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>How to: Use Docker Remote Contexts with SSH</title>
        <published>2025-10-10T00:00:00+00:00</published>
        <updated>2025-10-10T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/docker-remote-context/"/>
        <id>https://svnscha.de/posts/docker-remote-context/</id>
        <summary type="html">A simple and secure guide to configuring SSH-based Docker remote contexts for offloading resource-intensive Docker operations to a remote host while keeping your local environment lightweight.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/docker-remote-context/">&lt;h2 id=&quot;the-problem-when-your-laptop-becomes-a-jet-engine&quot;&gt;The Problem: When Your Laptop Becomes a Jet Engine&lt;/h2&gt;
&lt;p&gt;Working on resource-intensive Docker projects often turns laptops into jet engines. The fans spin up, the CPU overheats, and performance degrades significantly. This is especially problematic when working with limited resources - lightweight laptops, VDI sessions, or simply wanting to preserve your hardware.&lt;/p&gt;
&lt;p&gt;An SSH-based Docker context keeps the development tools on your local machine while running Docker workloads on a more capable remote host. Your laptop stays responsive and the remote system handles the heavier work.&lt;/p&gt;
&lt;h2 id=&quot;step-1-prerequisites&quot;&gt;Step 1: Prerequisites&lt;/h2&gt;
&lt;p&gt;Before we start, ensure you have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SSH access configured&lt;/strong&gt; to your remote Docker host with a proper SSH config entry like:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ~/.ssh/config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host docker-remote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  HostName 10.10.10.10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  IdentityFile ~/.ssh/id_rsa&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  User ubuntu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;Your user is in the docker group&lt;/strong&gt; on the remote host:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; usermod&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -aG&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; docker&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $USER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's all the setup needed! Now let's create the Docker context.&lt;/p&gt;
&lt;h2 id=&quot;step-2-create-the-ssh-based-docker-context&quot;&gt;Step 2: Create the SSH-Based Docker Context&lt;/h2&gt;
&lt;p&gt;Creating the Docker context is incredibly simple when you have SSH properly configured.&lt;/p&gt;
&lt;h3 id=&quot;create-your-ssh-based-remote-context&quot;&gt;Create Your SSH-Based Remote Context&lt;/h3&gt;
&lt;p&gt;Create a new context using your SSH config host:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; create&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; docker-remote&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; host=ssh://docker-remote&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it! Docker will use your existing SSH configuration, including the hostname, user, and identity file you've already set up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The beauty of this approach:&lt;/strong&gt; No daemon.json changes, no TCP configuration, no port forwarding, no firewall rules, no TLS certificate generation - just leverage your existing SSH security infrastructure. Simple and elegant!&lt;/p&gt;
&lt;h3 id=&quot;check-your-current-contexts&quot;&gt;Check Your Current Contexts&lt;/h3&gt;
&lt;p&gt;First, let's see what contexts you currently have:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll probably see something like:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NAME              DESCRIPTION                               DOCKER ENDPOINT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;default *         Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;desktop-linux     Docker Desktop                            npipe:////./pipe/dockerDesktopLinuxEngine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;desktop-windows   Docker Desktop                            npipe:////./pipe/dockerDesktopWindowsEngine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;docker-remote                                               ssh://docker-remote&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;test-the-connection&quot;&gt;Test the Connection&lt;/h3&gt;
&lt;p&gt;Let's verify everything works:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; docker-remote&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; container&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see your remote containers listed (or an empty list if none are running), congratulations! You're successfully connected to your remote Docker host via SSH.&lt;/p&gt;
&lt;h2 id=&quot;step-3-make-it-your-default-optional&quot;&gt;Step 3: Make It Your Default (Optional)&lt;/h2&gt;
&lt;p&gt;If you want to use the remote context by default, you can switch to it:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; docker-remote&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all your &lt;code&gt;docker&lt;/code&gt; commands will automatically use the remote host:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; container&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# This now runs on your remote host!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;environment-variable-override&quot;&gt;Environment Variable Override&lt;/h3&gt;
&lt;p&gt;For more granular control, you can also use the &lt;code&gt;DOCKER_CONTEXT&lt;/code&gt; environment variable:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Windows PowerShell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$env&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:DOCKER_CONTEXT = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;docker-remote&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Linux/macOS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; DOCKER_CONTEXT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;docker-remote&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach is particularly useful in CI/CD scenarios or when you want different shells to use different contexts.&lt;/p&gt;
&lt;h2 id=&quot;why-ssh-is-better&quot;&gt;Why SSH is Better&lt;/h2&gt;
&lt;p&gt;Using SSH for Docker remote contexts offers several advantages over TCP:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: All communication is encrypted by default&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: Leverages existing SSH keys and authentication methods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No daemon configuration&lt;/strong&gt;: No need to modify Docker daemon settings on the remote host&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firewall friendly&lt;/strong&gt;: Uses standard SSH port (22)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit trail&lt;/strong&gt;: SSH logging provides better security monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;switching-between-contexts&quot;&gt;Switching Between Contexts&lt;/h2&gt;
&lt;p&gt;Need to switch back to local Docker for something? Easy:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# List available contexts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Switch to local Docker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; default&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Or use a specific context for one command&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; container&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;bonus-powershell-function-for-quick-context-switching&quot;&gt;Bonus: PowerShell Function for Quick Context Switching&lt;/h2&gt;
&lt;p&gt;Here's a handy PowerShell function to make switching contexts even easier. Add this to your PowerShell profile for instant remote Docker activation:&lt;/p&gt;
&lt;h3 id=&quot;edit-your-powershell-profile&quot;&gt;Edit Your PowerShell Profile&lt;/h3&gt;
&lt;p&gt;First, open your PowerShell profile for editing:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;notepad &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$PROFILE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the file doesn't exist yet, PowerShell will ask if you want to create it - say yes.&lt;/p&gt;
&lt;h3 id=&quot;add-the-function&quot;&gt;Add the Function&lt;/h3&gt;
&lt;p&gt;Add this function to your profile:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; docker-remote-activate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    $env:DOCKER_CONTEXT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;docker-remote&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    Write-Host&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Docker context switched to remote host&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&quot;docker-remote&quot;&lt;/code&gt; with whatever you named your remote context.&lt;/p&gt;
&lt;h3 id=&quot;save-and-reload&quot;&gt;Save and Reload&lt;/h3&gt;
&lt;p&gt;Save the file and reload your PowerShell profile:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$PROFILE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;
&lt;p&gt;Now you can instantly activate remote Docker in any PowerShell session:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Activate remote Docker for this session only&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker-remote-activate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Now all Docker commands run remotely&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker ps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker images&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker build .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The beauty of this approach is that it only affects the current PowerShell session - open a new terminal window and you're back to local Docker by default!&lt;/p&gt;
&lt;h3 id=&quot;global-vs-terminal-specific-context-switching&quot;&gt;Global vs Terminal-Specific Context Switching&lt;/h3&gt;
&lt;p&gt;You have two ways to switch between local and remote Docker contexts:&lt;/p&gt;
&lt;h4 id=&quot;global-context-switching&quot;&gt;Global Context Switching&lt;/h4&gt;
&lt;p&gt;Affects all terminals system-wide:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Switch globally to remote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; docker-remote&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Switch back to local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; use&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; default&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;terminal-specific-context&quot;&gt;Terminal-Specific Context&lt;/h4&gt;
&lt;p&gt;Affects only the current terminal session:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Terminal 1: Remote context&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker-remote-activate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker ps  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# runs remotely&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Terminal 2: Still local context  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;docker ps  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# runs locally&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether you prefer global context switching or terminal-specific control, Docker remote contexts give you the flexibility to work exactly how you want.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Using SSH-based Docker remote contexts provides a secure, simple solution for offloading resource-intensive Docker operations to remote hosts. With just a single command and proper SSH configuration, you can transform your development workflow without compromising security.&lt;/p&gt;
&lt;p&gt;The SSH approach eliminates the complexity of daemon configuration while providing enterprise-grade security through encrypted communication and existing authentication infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secure by default (encrypted communication)&lt;/li&gt;
&lt;li&gt;No daemon configuration required&lt;/li&gt;
&lt;li&gt;Uses standard SSH authentication&lt;/li&gt;
&lt;li&gt;Works with existing SSH infrastructure&lt;/li&gt;
&lt;li&gt;Simple one-command setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/engine/manage-resources/contexts/&quot;&gt;Docker Context Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/engine/context/remote/&quot;&gt;Docker SSH Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ssh.com/academy/ssh/public-key-authentication&quot;&gt;SSH Key Authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>How to extend GitHub Copilot with prompt files</title>
        <published>2025-09-10T00:00:00+00:00</published>
        <updated>2025-09-10T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/howto-extend-copilot-with-prompt-files/"/>
        <id>https://svnscha.de/posts/howto-extend-copilot-with-prompt-files/</id>
        <summary type="html">Because copy-pasting prompts from your notes app is so last year. Let me show you how to turn your best prompts into reusable superpowers.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/howto-extend-copilot-with-prompt-files/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Remember when I went absolutely euphoric about &lt;a href=&quot;/posts/vscode-vibe-coding/&quot;&gt;vibe coding&lt;/a&gt;? Well, that honeymoon phase taught me one crucial lesson: &lt;strong&gt;context matters, and you really need to spend time writing good prompts.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At first, I was frantically scribbling prompts in my notes app like some kind of digital hoarder. Then I graduated to GitHub Copilot instruction files, adding rules like &quot;when asked for review, do this and that.&quot; But constantly switching between apps and copy-pasting prompts? &lt;strong&gt;I'm tired of it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GitHub Copilot's prompt files solve this by keeping reusable instructions in the repository.&lt;/p&gt;
&lt;h2 id=&quot;using-promptmd-files&quot;&gt;Using &lt;code&gt;.prompt.md&lt;/code&gt; Files&lt;/h2&gt;
&lt;p&gt;Prompt files turn your best prompts into reusable slash commands. VS Code gives you two flavors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workspace prompts&lt;/strong&gt; (&lt;code&gt;.github/prompts/&lt;/code&gt;): Travel with your repository, perfect for team-shared helpers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User prompts&lt;/strong&gt;: Stored in your VS Code profile, available everywhere for personal tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both become accessible as &lt;code&gt;/my-prompt&lt;/code&gt; directly in VS Code chat. Instead of hunting through notes for that brilliant review prompt, you just type &lt;code&gt;/svnscha-blog-review&lt;/code&gt; and boom, there it is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Prompt files are currently in public preview and may change in future releases.&lt;/p&gt;
&lt;h2 id=&quot;real-world-example-blog-review-prompt&quot;&gt;Real-World Example: Blog Review Prompt&lt;/h2&gt;
&lt;p&gt;Let me show you what this looks like in practice. I use project prefixes for workspace prompts and &lt;code&gt;/my-...&lt;/code&gt; for personal ones.&lt;/p&gt;
&lt;p&gt;For this blog, I created &lt;code&gt;/svnscha-blog-review&lt;/code&gt; - a specialized prompt that understands my writing style:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;agent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;description&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Review blog posts for svnscha.github.io with focus on style, grammar, and technical accuracy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;# Blog Post Review for svnscha.github.io&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;You are an expert editor reviewing a blog post for svnscha's technical blog.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Focus on:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;## Writing Style &amp;amp; Tone&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Consider using &quot;Why, You Ask?&quot; openings when it makes sense for the topic (not mandatory for all posts)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Keep the direct, slightly sarcastic but helpful voice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Ensure smooth transitions between sections&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; End posts with a short summary or practical next steps when they add value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;## Technical Accuracy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Verify code examples are correct and practical&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Check that technical explanations are clear and accurate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Ensure all file paths and commands are properly formatted&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;## Grammar &amp;amp; Spelling&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Fix any grammatical errors or typos&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Improve sentence structure where needed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Maintain consistency in technical terminology&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;## Content Structure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Ensure headings flow logically&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Verify examples support the main points&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Suggest a brief summary or actionable next steps when appropriate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Check that the conclusion ties back to the opening&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Provide specific, actionable feedback that maintains the author's voice while improving clarity and correctness.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This becomes available as &lt;code&gt;/svnscha-blog-review&lt;/code&gt; in VS Code, giving me instant access to a specialized editor that knows my style.&lt;/p&gt;
&lt;h2 id=&quot;smart-naming--location-strategy&quot;&gt;Smart Naming &amp;amp; Location Strategy&lt;/h2&gt;
&lt;p&gt;The naming convention is simple: &lt;strong&gt;project prefixes for workspace prompts, &lt;code&gt;/my-...&lt;/code&gt; for personal ones.&lt;/strong&gt; So &lt;code&gt;/svnscha-blog-review&lt;/code&gt; is specific to this blog, while &lt;code&gt;/my-commit&lt;/code&gt; works everywhere.&lt;/p&gt;
&lt;p&gt;Choose your location based on scope:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workspace prompts&lt;/strong&gt;: Team-shared, project-specific helpers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User prompts&lt;/strong&gt;: Personal tools that follow you across all projects&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dynamic-prompts-with-variables&quot;&gt;Dynamic Prompts with Variables&lt;/h2&gt;
&lt;p&gt;Make your prompts adapt to context with VS Code variables:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;# Code Review Prompt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Review the following code in ${file}:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;${selection}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Focus on:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Security vulnerabilities in ${fileBasename}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Performance implications &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6796E6&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; Code style consistency with ${workspaceFolderBasename} standards&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most useful variables include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workspace&lt;/strong&gt;: &lt;code&gt;${workspaceFolder}&lt;/code&gt;, &lt;code&gt;${workspaceFolderBasename}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File context&lt;/strong&gt;: &lt;code&gt;${file}&lt;/code&gt;, &lt;code&gt;${fileBasename}&lt;/code&gt;, &lt;code&gt;${fileDirname}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Selection&lt;/strong&gt;: &lt;code&gt;${selection}&lt;/code&gt;, &lt;code&gt;${selectedText}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the full list of available variables and advanced features, check out the &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/customization/prompt-files&quot;&gt;VS Code prompt files documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-your-prompt-arsenal&quot;&gt;Setting Up Your Prompt Arsenal&lt;/h2&gt;
&lt;p&gt;Getting started is simple:&lt;/p&gt;
&lt;h3 id=&quot;workspace-prompts&quot;&gt;Workspace Prompts&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;.github/prompts/&lt;/code&gt; in your repository&lt;/li&gt;
&lt;li&gt;Add your &lt;code&gt;.prompt.md&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Commit and push - your team now has access&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;user-prompts&quot;&gt;User Prompts&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Open Command Palette (&lt;code&gt;Ctrl+Shift+P&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Run &quot;Chat: New Prompt File&quot;&lt;/li&gt;
&lt;li&gt;Choose &quot;User profile&quot; for location&lt;/li&gt;
&lt;li&gt;Author and save your prompt&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both methods make prompts available with &lt;code&gt;/prompt-name&lt;/code&gt; in GitHub Copilot chat. Workspace prompts travel with your repository, while user prompts sync across devices with Settings Sync.&lt;/p&gt;
&lt;p&gt;For examples and community contributions, check out the &lt;a href=&quot;https://github.com/github/awesome-copilot/tree/main/prompts&quot;&gt;Awesome GitHub Copilot prompts collection&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Prompt files transform good prompting from an exclusive skill into a shared superpower. Your team gets expert-level guidance, your hotfix deployments get proper review, and your collective wisdom becomes instantly accessible with slash commands.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start small&lt;/strong&gt; - Convert your three most-used prompts to &lt;code&gt;.prompt.md&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose scope wisely&lt;/strong&gt; - Workspace prompts for teams, user prompts for personal tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use variables&lt;/strong&gt; - Make prompts dynamic with &lt;code&gt;${selection}&lt;/code&gt;, &lt;code&gt;${file}&lt;/code&gt;, and &lt;code&gt;${input:variableName}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow naming conventions&lt;/strong&gt; - Project prefixes for workspace, &lt;code&gt;/my-...&lt;/code&gt; for user prompts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explore the community&lt;/strong&gt; - Check out &lt;a href=&quot;https://github.com/github/awesome-copilot/tree/main/prompts&quot;&gt;Awesome GitHub Copilot prompts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read the docs&lt;/strong&gt; - &lt;a href=&quot;https://code.visualstudio.com/docs/copilot/customization/prompt-files&quot;&gt;VS Code prompt files documentation&lt;/a&gt; has the latest features&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The main benefit is simple: useful instructions live with the project and can be reused without copying them into every chat.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Get Started with uberAgent for Linux: Enterprise Observability Made Simple</title>
        <published>2025-08-26T00:00:00+00:00</published>
        <updated>2025-08-26T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/citrix-uberagent-getting-started-linux/"/>
        <id>https://svnscha.de/posts/citrix-uberagent-getting-started-linux/</id>
        <summary type="html">Finally! uberAgent Technology Preview 1 brings unified observability to Linux. Here's how I deployed it across my entire infrastructure using Ansible and what you can expect from this game-changing monitoring solution.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/citrix-uberagent-getting-started-linux/">&lt;h2 id=&quot;uberagent-for-linux-has-arrived&quot;&gt;uberAgent for Linux Has Arrived&lt;/h2&gt;
&lt;p&gt;After years of Windows getting all the monitoring love, &lt;a href=&quot;https://www.citrix.com/downloads/citrix-early-access-release/ear/uberagent-preview.html&quot;&gt;uberAgent for Linux Technology Preview 1&lt;/a&gt; has arrived - and it's everything we have been waiting for.&lt;/p&gt;
&lt;p&gt;I run a multi-site setup with MikroTik routers and WireGuard tunnels connecting my home, coworking space, and cloud infrastructure. Systems such as &lt;code&gt;sso-1&lt;/code&gt;, &lt;code&gt;drive-1&lt;/code&gt;, and &lt;code&gt;splunk-1&lt;/code&gt; communicate over an internal network through a gateway server. I wanted better visibility into these server workloads: Docker hosts, reverse proxies, service communication, and resource use across the environment.&lt;/p&gt;
&lt;p&gt;Having uberAgent finally extend from Windows to my entire Linux infrastructure fills a gap I've been dealing with for years.&lt;/p&gt;
&lt;h2 id=&quot;why-linux-infrastructure-monitoring-just-got-interesting&quot;&gt;Why Linux Infrastructure Monitoring Just Got Interesting&lt;/h2&gt;
&lt;p&gt;You've got a beautiful Linux infrastructure running critical workloads, but your monitoring looks like Dr. Frankenstein's laboratory. A little Prometheus here, some custom scripts there, maybe some Grafana dashboards that kinda-sorta show you what's happening. You're stitching together metrics from five different tools just to answer simple questions like &quot;Why is this service slow?&quot; or &quot;Which application is hammering my database?&quot;&lt;/p&gt;
&lt;p&gt;Meanwhile, your Windows colleagues are living it up with comprehensive uberAgent dashboards that actually make sense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not anymore.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;getting-started-ubuntu-installation&quot;&gt;Getting Started: Ubuntu Installation&lt;/h2&gt;
&lt;p&gt;Currently, uberAgent for Linux ships as &lt;code&gt;.deb&lt;/code&gt; packages, making Ubuntu (and Debian-based) installations straightforward. Here's how to get it running on your systems.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Please check the &lt;a href=&quot;https://docs.citrix.com/en-us/uberagent/7-4-0/linux-tech-preview.html&quot;&gt;official documentation&lt;/a&gt; for the latest instructions.&lt;/p&gt;
&lt;p&gt;So, before diving in, ensure your systems have the basics:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; apt&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; wget&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; unzip&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;manual-installation-process&quot;&gt;Manual Installation Process&lt;/h3&gt;
&lt;p&gt;First, extract the uberAgent package from the distribution ZIP:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Create working directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /tmp/uberagent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /tmp/uberagent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Extract the ZIP (assuming you've downloaded it)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;unzip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; uberAgent-Linux-TP1.zip&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Find and install the .deb package&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -name&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;*uberagent*amd64*.deb&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -exec&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; dpkg&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -i&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; {}&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Fix any dependency issues&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; apt-get&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -f&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;configuration-directory-structure&quot;&gt;Configuration Directory Structure&lt;/h3&gt;
&lt;p&gt;Once installed, uberAgent requires its configuration structure at &lt;code&gt;/etc/uberAgent/&lt;/code&gt;. The key files you'll work with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/uberAgent/uberAgent.conf&lt;/code&gt; - Main configuration file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/uberAgent/&lt;/code&gt; - Additional configuration files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/log/uberAgent/&lt;/code&gt; - Log files location&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;basic-configuration-setup&quot;&gt;Basic Configuration Setup&lt;/h3&gt;
&lt;p&gt;Download the standard configuration bundle:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Download the latest config bundle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;wget&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -O&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /tmp/uberAgent.uAConfig&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;  https://github.com/vastlimits/uberAgent-config/raw/refs/heads/version/7.4/config-dist/uberAgent.uAConfig&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Extract to config directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; unzip&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -o&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /tmp/uberAgent.uAConfig&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /etc/uberAgent/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;splunk-integration&quot;&gt;Splunk Integration&lt;/h3&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/uberAgent/uberAgent.conf&lt;/code&gt; to configure your Splunk HEC endpoint:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ini&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[Receiver]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = Splunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = Splunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Protocol&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = HTTP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Servers&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = https://your-splunk-hec-url:8088&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;RESTToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = your-hec-token-here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;service-management&quot;&gt;Service Management&lt;/h3&gt;
&lt;p&gt;Start and enable the service:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; uberAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; uberAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Check status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; status&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; uberAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# View logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; journalctl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; uberAgent&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -f&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;scaling-with-ansible-key-automation-snippets&quot;&gt;Scaling with Ansible: Key Automation Snippets&lt;/h2&gt;
&lt;p&gt;For larger deployments, Ansible makes this process repeatable across your infrastructure. Here are the essential patterns I use:&lt;/p&gt;
&lt;h3 id=&quot;package-installation-task&quot;&gt;Package Installation Task&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Install uberAgent from ZIP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  block&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Transfer and extract ZIP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      copy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;{{ uberagent_local_zip }}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/uberagent.zip&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Unarchive ZIP on target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      unarchive&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/uberagent.zip&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/uberagent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        remote_src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Find .deb package&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      find&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        paths&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/uberagent&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        patterns&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;*uberagent*amd64*.deb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        recurse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;deb_search&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Install via dpkg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      command&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;dpkg -i {{ (deb_search.files | map(attribute='path') | list)[0] }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;dpkg_install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      changed_when&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;dpkg_install.rc == 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      failed_when&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;dpkg_install.rc not in [0,1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Fix dependencies if needed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      apt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;present&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        update_cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      when&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;dpkg_install.rc == 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;configuration-deployment&quot;&gt;Configuration Deployment&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Deploy uberAgent configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  block&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Download config bundle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      get_url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;{{ uberagent_config_bundle_url }}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/uberAgent.uAConfig&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        mode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;0644&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Extract configuration bundle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      unarchive&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/uberAgent.uAConfig&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/etc/uberAgent/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        remote_src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        extra_opts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-o&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;-q&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;splunk-receiver-configuration&quot;&gt;Splunk Receiver Configuration&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Configure Splunk receiver&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  block&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Define Splunk receiver block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      set_fact&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        uberagent_receiver_block&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;-&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          [Receiver]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          Name = {{ uberagent_receiver_name }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          Type = Splunk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          Protocol = HTTP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          Servers = {{ uberagent_splunk_hec_url }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          RESTToken = {{ uberagent_splunk_hec_token }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Replace existing receiver configuration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      replace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/etc/uberAgent/uberAgent.conf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        regexp&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'(?ms)^\[Receiver\]\nName *=.*?\nType *=.*?\nProtocol *=.*?\nServers *=.*?\nRESTToken *=.*?\n'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        replace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;{{ uberagent_receiver_block | regex_replace('&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;n', '&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;') }}&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;receiver_replace&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      notify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;restart uberAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Add receiver block if none existed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      blockinfile&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/etc/uberAgent/uberAgent.conf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        marker&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;;; {mark} ANSIBLE MANAGED RECEIVER BLOCK&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        block&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;{{ uberagent_receiver_block }}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        insertafter&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      when&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;not receiver_replace.changed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      notify&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;restart uberAgent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These Ansible snippets give you the foundation - adapt the variables and structure to match your environment and deployment needs.&lt;/p&gt;
&lt;h3 id=&quot;performance-impact-analysis&quot;&gt;Performance Impact Analysis&lt;/h3&gt;
&lt;p&gt;Let's start with the most important question: what does uberAgent actually cost your infrastructure? This analysis shows uberAgent's resource consumption across multiple hosts over a full week of monitoring.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/Analyze-data-over-time-Splunk-9-0-7-08-26-2025_08_50_PM.png&quot; alt=&quot;Analyze Data Over Time&quot;&gt;&lt;/p&gt;
&lt;p&gt;The results are impressive: uberAgent delivers comprehensive monitoring with minimal overhead. You're looking at resource consumption so low that you'll question whether it's actually collecting all that data.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPU impact&lt;/strong&gt;: Consistently under 1% across all monitored systems&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory footprint&lt;/strong&gt;: Typically 20-50MB per agent - less than a single browser tab&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is monitoring software that enhances your visibility without becoming a performance bottleneck.&lt;/p&gt;
&lt;h3 id=&quot;infrastructure-discovery&quot;&gt;Infrastructure Discovery&lt;/h3&gt;
&lt;p&gt;Your complete infrastructure topology emerges automatically as agents come online. Having a machine inventory I can rely on, next to my Ansible inventory is great. I really like this one.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/Machine-Inventory-Splunk-9-0-7-08-26-2025_08_39_PM.png&quot; alt=&quot;Machine Inventory&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;network-flow-visualization&quot;&gt;Network Flow Visualization&lt;/h3&gt;
&lt;p&gt;Understanding inter-service communication becomes trivial when you can see exactly which services talk to each other and how much traffic flows between them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/Machine-Network-Communication-Splunk-9-0-7-08-26-2025_08_35_PM.png&quot; alt=&quot;Machine Network Communication&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;application-level-troubleshooting&quot;&gt;Application-Level Troubleshooting&lt;/h3&gt;
&lt;p&gt;When applications start misbehaving, you get immediate visibility into network-related issues that traditional monitoring tools miss entirely.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/Application-Network-Issues-Splunk-9-0-7-08-26-2025_08_38_PM.png&quot; alt=&quot;Application Network Issues&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;This is it - uberAgent for Linux doesn't just add another platform; it &lt;strong&gt;completes the entire monitoring ecosystem&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Think about what this means: your existing Windows and macOS monitoring now gets perfectly extended with comprehensive Linux coverage. We're talking about &lt;strong&gt;Linux VDAs&lt;/strong&gt; running in your Citrix environments, &lt;strong&gt;non-VDA Linux desktops&lt;/strong&gt; handling critical workloads, &lt;strong&gt;Linux thin clients&lt;/strong&gt; connecting to your Citrix infrastructure - suddenly every endpoint in your environment speaks the same monitoring language!&lt;/p&gt;
&lt;p&gt;The part I am most interested in is &lt;strong&gt;server workloads&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Docker hosts, Kubernetes clusters, database servers, message queues, reverse proxies, microservices, and load balancers can now use the same uberAgent monitoring approach as Windows systems. That makes it possible to view Citrix infrastructure, bare-metal servers, and cloud-native workloads in one place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is infrastructure observability done right&lt;/strong&gt;: one agent, one data format, one set of dashboards covering your entire technology stack from end-user experience down to kernel-level performance metrics. Citrix environments, non-Citrix infrastructure, hybrid cloud setups, on-premises data centers - it's all there, all visible, all making sense together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The deployment stays effortless&lt;/strong&gt; (thanks to Ansible automation), &lt;strong&gt;performance impact remains minimal&lt;/strong&gt;, and &lt;strong&gt;the insights now span absolutely everything&lt;/strong&gt;. No more tool sprawl, no more monitoring blind spots, no more wondering if that user complaint is related to the application, the network, or the underlying infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ready to achieve monitoring enlightenment?&lt;/strong&gt; Deploy this Technology Preview and watch your entire infrastructure transform from a collection of mysterious black boxes into a completely transparent, interconnected system where every component tells its story.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;After deploying uberAgent across my infrastructure, I quickly noticed one challenge: data volume. While the insights are invaluable, my home lab generates monitoring data at a pace that exceeds typical residential environments.&lt;/p&gt;
&lt;p&gt;With Splunk's free licensing constraints (500MB/day), I am implementing &lt;a href=&quot;https://docs.citrix.com/en-us/uberagent/7-4-0/uxm-features-configuration/event-data-filtering&quot;&gt;Event Data Filters&lt;/a&gt; as a solution and add a optimized data volume configuration. However, I'm already contemplating the next evolution - developing a custom backend infrastructure with purpose-built visualization dashboards specifically optimized for my environment.&lt;/p&gt;
&lt;p&gt;This technical challenge would be particularly engaging - balancing comprehensive visibility with resource efficiency. For now, though, the filters keep things manageable while I plan the next iteration of my monitoring architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Disclosure Note:&lt;/strong&gt;
I am a Citrix employee. However, this post represents my personal enthusiasm and experience with the product, not an official company perspective. I wrote this entirely of my own volition because I'm genuinely excited about these capabilities.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>A Git feature I should've met sooner!</title>
        <published>2025-04-04T00:00:00+00:00</published>
        <updated>2025-04-04T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/git-worktree/"/>
        <id>https://svnscha.de/posts/git-worktree/</id>
        <summary type="html">I was living in stash-hell and didn't even know it.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/git-worktree/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Because I thought I knew Git.
Like really. Branches? Merged. Rebased. Cherry-picked. Heck, I've even force-pushed into production (don't judge me). I've danced the &lt;code&gt;git stash&lt;/code&gt; tango more times than I care to admit, and I've rage-quit after realizing I checked out the wrong branch with uncommitted changes.&lt;/p&gt;
&lt;p&gt;But then-then-I discovered &lt;code&gt;git worktree&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And everything changed.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-with-the-normal-way&quot;&gt;The Problem With the &quot;Normal&quot; Way™&lt;/h3&gt;
&lt;p&gt;Let's say you're working on a feature. You're knee-deep in uncommitted changes, and suddenly you need to switch to another branch to hotfix or review something. What do you do?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git stash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git checkout other-branch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Try to remember what you were doing in your branch later.&lt;/li&gt;
&lt;li&gt;Forget what you stashed and why.&lt;/li&gt;
&lt;li&gt;Cry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the standard Git workflow we all just kind of accept. It's chaotic good at best.&lt;/p&gt;
&lt;h3 id=&quot;enter-git-worktree&quot;&gt;Enter: &lt;code&gt;git worktree&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;What if I told you... you could just check out multiple branches at the same time? Each in its own separate folder. No stash. No backflips. No existential dread.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git worktree&lt;/code&gt; is a built-in Git feature that lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check out multiple branches at once&lt;/li&gt;
&lt;li&gt;Work on each one in its own directory&lt;/li&gt;
&lt;li&gt;Keep your mental state and filesystem intact&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;examples&quot;&gt;Examples&lt;/h3&gt;
&lt;p&gt;Create a new worktree for a feature branch&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; worktree&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ../feature-x&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; origin/feature-x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This checks out the &lt;code&gt;feature-x&lt;/code&gt; branch (fetched from &lt;code&gt;origin&lt;/code&gt;) into a new directory &lt;code&gt;../feature-x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If it's a new feature you start locally, do this:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; worktree&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ../feature-x&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -b&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; feature-x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a new local branch &lt;code&gt;feature-x&lt;/code&gt; from your current &lt;code&gt;HEAD&lt;/code&gt; (usually &lt;code&gt;main&lt;/code&gt;) and checks it out into &lt;code&gt;../feature-x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Wow. 🥳 No more &quot;wait lemme stash real quick.&quot;&lt;/p&gt;
&lt;p&gt;Now you can commit your fix, push it, and go back to your mess later. Like a civilized developer.&lt;/p&gt;
&lt;h4 id=&quot;list-all-worktrees&quot;&gt;List all worktrees&lt;/h4&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; worktree&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because you will forget what you spun up after the third coffee.&lt;/p&gt;
&lt;h3 id=&quot;whats-the-catch&quot;&gt;What's the Catch?&lt;/h3&gt;
&lt;p&gt;Honestly? Not much. A few things to keep in mind:&lt;/p&gt;
&lt;p&gt;You can't check out the same branch twice. Git will yell at you. That's fair.&lt;/p&gt;
&lt;p&gt;Deleting a worktree is easy: just &lt;code&gt;git worktree remove ../feature-x&lt;/code&gt; (the directory) and that's it.&lt;/p&gt;
&lt;h3 id=&quot;why-you-need-this-in-your-life&quot;&gt;Why You Need This in Your Life&lt;/h3&gt;
&lt;p&gt;If you're working on multiple features, doing code reviews locally, fixing stuff mid-feature, or just trying to keep your brain from melting-&lt;code&gt;git worktree&lt;/code&gt; is the gift you didn't know Git included.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No more stash juggling.&lt;/li&gt;
&lt;li&gt;No more &quot;which terminal had which branch again?&quot;&lt;/li&gt;
&lt;li&gt;No more &quot;accidentally committed to main&quot; horror stories.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just clean, parallel workspaces from a single clone.&lt;/p&gt;
&lt;p&gt;Use it. Love it. Regret not learning it sooner.
I know I did.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Windows: Install integrated OpenSSH Server</title>
        <published>2025-04-03T00:00:00+00:00</published>
        <updated>2025-04-03T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/windows-openssh-server/"/>
        <id>https://svnscha.de/posts/windows-openssh-server/</id>
        <summary type="html">Enable built-in OpenSSH server on Windows for remote access and VSCode Remote development</summary>
        <content type="html" xml:base="https://svnscha.de/posts/windows-openssh-server/">&lt;p&gt;Tired of everyone SSHing into Linux? Flip the script: SSH into Windows from your Linux box. Works great with VSCode Remote too. Here's how to enable the built-in OpenSSH server on Windows.&lt;/p&gt;
&lt;h3 id=&quot;step-1-check-installed-openssh-components&quot;&gt;Step 1: Check Installed OpenSSH Components&lt;/h3&gt;
&lt;p&gt;First, open a PowerShell window with administrator privileges. Let's see what's already installed:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Get-WindowsCapability&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -Online&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where-Object&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Name&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -like&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; 'OpenSSH*'&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might see output indicating that the client is installed but not the server:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Name  : OpenSSH.Client~~~~0.0.1.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;State : Installed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Name  : OpenSSH.Server~~~~0.0.1.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;State : NotPresent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-install-the-openssh-server&quot;&gt;Step 2: Install the OpenSSH Server&lt;/h3&gt;
&lt;p&gt;If the server isn't present, let's bring it on board. Run:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add-WindowsCapability&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -Online&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -Name&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; OpenSSH.Server~~~~0.0.1.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-start-and-enable-the-ssh-server&quot;&gt;Step 3: Start and Enable the SSH Server&lt;/h3&gt;
&lt;p&gt;Now, let's get the &lt;code&gt;sshd&lt;/code&gt; service up and running:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Start-Service&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; sshd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And ensure it starts automatically with Windows:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Set-Service&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -Name&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; sshd&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -StartupType&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; 'Automatic'&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>C++: How to measure code coverage with lcov and GTest</title>
        <published>2025-03-25T00:00:00+00:00</published>
        <updated>2025-03-25T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/cpp-code-coverage/"/>
        <id>https://svnscha.de/posts/cpp-code-coverage/</id>
        <summary type="html">Because you want to know which parts of your C++ code are actually being tested.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/cpp-code-coverage/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Because you want to know which parts of your C++ code are actually being tested – not just assumed to be. Code coverage isn't about perfection, but about confidence. And with the right setup, you can easily visualize what's being tested and what's quietly collecting dust.&lt;/p&gt;
&lt;p&gt;In this guide, we'll walk through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting up &lt;code&gt;lcov&lt;/code&gt; and GTest&lt;/li&gt;
&lt;li&gt;Writing effective coverage-focused tests&lt;/li&gt;
&lt;li&gt;Running the tests and generating reports&lt;/li&gt;
&lt;li&gt;Interpreting results and achieving real insight&lt;/li&gt;
&lt;li&gt;Automating it all via GitHub Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-try-it-yourself&quot;&gt;📦 Try It Yourself&lt;/h3&gt;
&lt;p&gt;Check out the working example repository here:&lt;/p&gt;
&lt;p&gt;👉 &lt;a href=&quot;https://github.com/svnscha/cpp-coverage-example&quot;&gt;github.com/svnscha/cpp-coverage-example&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A minimal &lt;code&gt;MyQueue&lt;/code&gt; class&lt;/li&gt;
&lt;li&gt;Unit tests using GTest&lt;/li&gt;
&lt;li&gt;Full &lt;code&gt;lcov&lt;/code&gt; and &lt;code&gt;genhtml&lt;/code&gt; integration&lt;/li&gt;
&lt;li&gt;A GitHub Actions workflow for automated code coverage with a threshold&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-install-required-tools&quot;&gt;1. Install Required Tools&lt;/h3&gt;
&lt;p&gt;Make sure your environment has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lcov&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A C++ compiler (GCC or Clang)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmake&lt;/code&gt;, &lt;code&gt;ninja&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GTest (or install &lt;code&gt;libgtest-dev&lt;/code&gt; on Ubuntu)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-build-with-coverage-instrumentation&quot;&gt;2. Build with Coverage Instrumentation&lt;/h3&gt;
&lt;p&gt;To generate coverage data, compile your code with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--coverage&lt;/code&gt;: Coverage instrumentation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-g&lt;/code&gt;: Debug information&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-O0&lt;/code&gt;: No optimization (to avoid inlining, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-minimal-cmake-snippet&quot;&gt;🛠 Minimal CMake Snippet&lt;/h4&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cmake&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;option&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(ENABLE_COVERAGE &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Enable code coverage reporting&quot;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; OFF&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;function&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(enable_coverage &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;target&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(ENABLE_COVERAGE &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;AND&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; CMAKE_CXX_COMPILER_ID &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;MATCHES&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;GNU|Clang&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        target_compile_options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${target}&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; PRIVATE --coverage -O0 -g)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        target_link_options(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;${target}&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; PRIVATE --coverage)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    endif&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;endfunction&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;enable_testing&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;add_library&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(MyLibrary ...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;add_executable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(MyTests ...)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;target_link_libraries&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(MyTests PRIVATE MyLibrary GTest::gtest_main)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;include&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(GTest)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;gtest_discover_tests(MyTests)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;enable_coverage(MyLibrary)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;enable_coverage(MyTests)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-write-tests-that-actually-trigger-all-code-paths&quot;&gt;3. Write Tests That Actually Trigger All Code Paths&lt;/h3&gt;
&lt;p&gt;Here’s a small utility class as an example:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; MyQueue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; val&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        _q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;val&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Pop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        _q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    bool&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; IsEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; _q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(); }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;private:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;    std&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;queue&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now test both normal and edge cases:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TEST&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;MyQueueTest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;PopWhenEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;    MyQueue&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Pop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt; // Hit the early return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    EXPECT_TRUE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TEST&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;MyQueueTest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;PushAndPop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;    MyQueue&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Push&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;42&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    EXPECT_FALSE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Pop&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    EXPECT_TRUE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;q&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-run-tests-and-generate-coverage-report&quot;&gt;4. Run Tests and Generate Coverage Report&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cmake&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DENABLE_COVERAGE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ON&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DCMAKE_BUILD_TYPE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Debug&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cmake&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;build&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ctest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;failure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then generate the coverage report:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cpp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lcov&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;directory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; . --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;capture&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; coverage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;rc&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; geninfo_auto_base&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ignore&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; mismatch&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;lcov&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;remove&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; coverage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '/usr/*'&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '*/tests/*'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;file&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; coverage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;filtered&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;genhtml&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; coverage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;filtered&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;info&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; --&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;directory&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; coverage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;report&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open this in your browser:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;build/coverage-report/index.html&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/cpp-coverage-example.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h3 id=&quot;5-automation&quot;&gt;5. Automation&lt;/h3&gt;
&lt;p&gt;Now, having the coverage report in place is a great start. But it's not enough to just have the coverage report. You need to automate it so that every time you run your tests, you get a detailed report of what was covered and what wasn't.&lt;/p&gt;
&lt;p&gt;To get started, you can setup a GitHub workflow with threshold tests, upload the reports or do post-processing on the report.&lt;/p&gt;
&lt;p&gt;Either way, the key is to have a way to run tests and generate reports. Here's an example workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/svnscha/cpp-coverage-example/blob/main/.github/workflows/coverage.yml&quot;&gt;.github/workflows/coverage.yml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/svnscha/cpp-coverage-example/pull/1&quot;&gt;Example Pull Request failing because of missing coverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/svnscha/cpp-coverage-example/pull/2&quot;&gt;Example Pull Request passing coverage tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What You Did&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Install&lt;/td&gt;
&lt;td&gt;Set up &lt;code&gt;lcov&lt;/code&gt;, gtest, gcc, and cmake&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Build&lt;/td&gt;
&lt;td&gt;Compiled with --coverage and no optimizations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Test&lt;/td&gt;
&lt;td&gt;Hit both happy paths and edge cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Report&lt;/td&gt;
&lt;td&gt;Generated detailed HTML coverage report&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Code coverage doesn't guarantee perfect tests, but it gives you visibility. When paired with thoughtful test cases (especially edge cases), it helps ensure your code behaves correctly - even when things go sideways.&lt;/p&gt;
&lt;p&gt;Even small utility classes deserve this level of care.&lt;/p&gt;
&lt;p&gt;And with &lt;code&gt;lcov&lt;/code&gt;, you can turn &quot;I think I tested this&quot; into &quot;Yes, this line has been executed 12 times during CI.&quot;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Exploring AI: Self Hosted LLM</title>
        <published>2025-03-17T00:00:00+00:00</published>
        <updated>2025-03-17T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/self-hosted-llm/"/>
        <id>https://svnscha.de/posts/self-hosted-llm/</id>
        <summary type="html">Because running your own AI is the real power move.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/self-hosted-llm/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;So, I decided to self-host an LLM. Why? Every time you use an online AI model, you're handing over your data to some company. Whether it's casual conversations, coding snippets, or business-related queries, everything you type is potentially being logged, analyzed, or even used to train future models. No thanks.&lt;/p&gt;
&lt;p&gt;Instead, I prefer to keep things local. Also, I have an RTX ADA 4000 with 20GB of memory sitting here, so why not put it to good use?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; makes it easy to run LLMs locally. I combine it with &lt;a href=&quot;https://github.com/open-webui/open-webui&quot;&gt;Open WebUI&lt;/a&gt; for the user interface and Nginx for remote access.&lt;/p&gt;
&lt;p&gt;Here is the setup.&lt;/p&gt;
&lt;h3 id=&quot;step-1-install-ollama&quot;&gt;Step 1: Install Ollama&lt;/h3&gt;
&lt;p&gt;Ollama makes deploying LLMs locally ridiculously simple. Here's how to install it:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://ollama.com/install.sh&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install Ollama and set up everything you need to start running models locally. Want to make sure it's working? Just run:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ollama&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; codellama:13b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see an interactive prompt, congrats - you've got a local LLM running!&lt;/p&gt;
&lt;h3 id=&quot;step-2-install-open-webui&quot;&gt;Step 2: Install Open WebUI&lt;/h3&gt;
&lt;p&gt;Ollama is great, but a web interface makes it even better. That's where Open WebUI comes in. It gives you a sleek, chat-like interface to interact with your models.&lt;/p&gt;
&lt;p&gt;To install Open WebUI manually without Docker, follow these steps:&lt;/p&gt;
&lt;h4 id=&quot;1-create-a-virtual-environment&quot;&gt;1. Create a Virtual Environment&lt;/h4&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;python3&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; venv&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/openwebui-venv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/openwebui-venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-install-open-webui&quot;&gt;2. Install Open WebUI&lt;/h4&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; open-webui&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-create-a-systemd-service&quot;&gt;3. Create a Systemd Service&lt;/h4&gt;
&lt;p&gt;To make sure Open WebUI runs on startup, create a systemd service file:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; nano&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /etc/systemd/system/openwebui.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste the following content:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Description&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Open&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; WebUI&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;After&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;network.target&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WorkingDirectory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/home/&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/openwebui-venv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ExecStart&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/home/&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/openwebui-venv/bin/open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Restart&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;always&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WantedBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and exit, then reload systemd and enable the service:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; daemon-reload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; openwebui.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; openwebui.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-reverse-proxy-with-nginx&quot;&gt;Step 3: Reverse Proxy with Nginx&lt;/h3&gt;
&lt;p&gt;Now, let's make accessing our LLM easier by setting up an Nginx reverse proxy. This way, we can reach Open WebUI without exposing it directly.&lt;/p&gt;
&lt;p&gt;Here's a basic Nginx config:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;server {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    listen 443 ssl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    server_name chat.example.com;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    location / {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_pass http://localhost:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_set_header Host $host;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_set_header X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload Nginx with:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; restart&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can access your self-hosted LLM via &lt;code&gt;https://chat.example.com&lt;/code&gt;. Fancy.&lt;/p&gt;
&lt;h3 id=&quot;you-own-your-data-now&quot;&gt;You Own Your Data Now&lt;/h3&gt;
&lt;p&gt;One of the biggest advantages of self-hosting an LLM? Your data stays with you.&lt;/p&gt;
&lt;p&gt;No sending queries to an external API, no third-party tracking what you're asking, no potential leaks of sensitive information. It's all running on your hardware, fully under your control. Whether you're experimenting with code, processing confidential documents, or just having fun chatting with AI, everything stays local.&lt;/p&gt;
&lt;h3 id=&quot;model-sizes--performance&quot;&gt;Model Sizes &amp;amp; Performance&lt;/h3&gt;
&lt;p&gt;Of course, different models come with different memory requirements. Here's what I'm running on my RTX ADA 4000 and how much VRAM they use:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NAME             ID              SIZE      PROCESSOR    UNTIL              &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;codellama:7b     8fdf8f752f6e    9.4 GB    100% GPU     2 minutes from now    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;codellama:13b    9f438cb9cd58    15 GB     100% GPU     4 minutes from now    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gemma3:12b       6fd036cefda5    13 GB     100% GPU     4 minutes from now    &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means I can comfortably run mid-sized models like &lt;code&gt;codellama:13b&lt;/code&gt; while keeping things snappy.&lt;/p&gt;
&lt;h3 id=&quot;choosing-the-right-gpu&quot;&gt;Choosing the Right GPU&lt;/h3&gt;
&lt;p&gt;Picking the right GPU is all about balancing performance, VRAM, and cost - because, let's be honest, unless you're running an AI research lab, you're not dropping $30,000 on an H100.&lt;/p&gt;
&lt;p&gt;Here's a quick breakdown of solid options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NVIDIA RTX 3090 - Powerful, with 24 GB of VRAM, but it's last-gen. ($$)&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 4090 - Even more powerful, with 24 GB of VRAM and better efficiency. ($$$)&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 5090 - Even more and more powerful, with 24 GB of VRAM and better efficiency. ($$$$)&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 4000 ADA Generation - Less powerful, but 20 GB of VRAM, and a low-profile card. ($)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, why the ADA 4000? While the RTX 5090 is the fastest in raw compute power, VRAM is king for training AI models. The ADA 4000's 20 GB VRAM gives you enough room for Stable Diffusion training, larger batch sizes, and AI experiments, without hitting the limits of other consumer GPUs such as 3080 (10 GB).&lt;/p&gt;
&lt;p&gt;Performance-wise, the 4090 and 5090 has more horsepower, but for training workloads where memory matters more than raw speed, the ADA 4000 is the more practical and cost-efficient choice. Plus, lower power consumption makes it a better long-term option if you're running AI workloads frequently. Also, the physical size - I mean it's a low profile card. Small, fits perfectly into any case.&lt;/p&gt;
&lt;p&gt;For my needs, the RTX 4000 Ada offered a useful balance of VRAM, price, power use, and size.&lt;/p&gt;
&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;
&lt;h4 id=&quot;asking-codellama13b&quot;&gt;Asking codellama:13b&lt;/h4&gt;
&lt;p&gt;Prompt: &lt;code&gt;implement fibonacci in python and also some unit tests using pytest&lt;/code&gt;&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/codellama-13b-fibonacci.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h4 id=&quot;asking-gemma312b&quot;&gt;Asking gemma3:12b&lt;/h4&gt;
&lt;p&gt;Prompt: &lt;code&gt;implement fibonacci in python and also some unit tests using pytest&lt;/code&gt;&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/gemma3-12b-fibonacci.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;With an RTX ADA 4000, Ollama, Open WebUI, and an Nginx reverse proxy, I now have an AI-powered assistant running entirely on my own hardware. No subscriptions, no cloud dependencies, just raw, local AI power. If you're serious about AI and privacy, setting this up is a no-brainer. Give it a try, and let your GPU do some work.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Moving to Fedora: Automating Discord Updates with Ansible</title>
        <published>2024-11-15T00:00:00+00:00</published>
        <updated>2024-11-15T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/fedora-discord-ansible/"/>
        <id>https://svnscha.de/posts/fedora-discord-ansible/</id>
        <summary type="html">Because life's too short to manually update Discord every week.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/fedora-discord-ansible/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;So, you've found yourself running Fedora and decided to install Discord, huh? Great choice  -  Fedora is fantastic. But then you realize Discord's tarball updates are a hassle. Every week, it's the same story: download, extract, and pray you didn't miss a step. Tedious, right?&lt;/p&gt;
&lt;p&gt;I use the following Ansible playbook to install and update Discord on Fedora. It handles the repetitive steps and makes the setup easy to reuse on another machine.&lt;/p&gt;
&lt;h2 id=&quot;step-1-understanding-the-process&quot;&gt;Step 1: Understanding the Process&lt;/h2&gt;
&lt;p&gt;Discord on Linux isn't as straightforward as we'd like. Flatpak is an option, but it can be slow with updates or cause compatibility issues. For those of us who prefer the direct tarball approach, we need a way to automate the mess. With Ansible, we can ensure Discord is always up-to-date with just one command. No more hunting for the latest download link or worrying about cleaning up old versions.&lt;/p&gt;
&lt;h2 id=&quot;step-2-the-playbook&quot;&gt;Step 2: The Playbook&lt;/h2&gt;
&lt;p&gt;Here's an Ansible playbook to make your life easier. It handles everything  -  downloading the tarball, extracting it to the proper location, and cleaning up afterward.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Install and Update Discord on Fedora 41 using Ansible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  hosts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  become&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  tasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Ensure /usr/share/discord is removed if it exists&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      file&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/usr/share/discord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;absent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Download Discord tarball&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      get_url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://discord.com/api/download/stable?platform=linux&amp;amp;format=tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/discord.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        mode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;0644&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Create /usr/share/discord directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      file&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/usr/share/discord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;directory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        mode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;0755&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Extract Discord tarball to /usr/share/discord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      unarchive&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/discord.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/usr/share/discord/&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        remote_src&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        extra_opts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;--strip-components=1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      args&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        creates&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/usr/share/discord/discord&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Clean up temporary files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      file&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/tmp/discord.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;absent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-3-what-this-playbook-does&quot;&gt;Step 3: What This Playbook Does&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Step 1: If Discord is already installed, it removes the existing files to prevent conflicts.&lt;/li&gt;
&lt;li&gt;Step 2: It downloads the latest stable Discord tarball directly from the official API.&lt;/li&gt;
&lt;li&gt;Step 3: Creates the required directory at &lt;code&gt;/usr/share/discord&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Step 4: Extracts the tarball into &lt;code&gt;/usr/share/discord&lt;/code&gt;, stripping unnecessary folder components.&lt;/li&gt;
&lt;li&gt;Step 5: Cleans up temporary files, leaving your system tidy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;a-note-about-bash-aliases&quot;&gt;A Note About Bash Aliases&lt;/h2&gt;
&lt;p&gt;If you're like me and love shortcuts, consider adding a bash alias to streamline running the playbook. This way, updating Discord becomes as simple as typing a single command. Here's how you can set it up:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cat&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/.bashrc.d/alias.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ansible-venv&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;. ~/repos/ansible/.venv/bin/activate&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;alias&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; update-discord&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;ansible-venv &amp;amp;&amp;amp; ansible-playbook ~/repos/ansible/update-discord.yml --connection=local -K&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this in place:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ansible-venv&lt;/code&gt; activates your virtual environment for Ansible.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update-discord&lt;/code&gt; runs the playbook with local connection and prompts for sudo permissions when needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, keeping Discord updated is literally a one-liner. Perfect, right?&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;The same playbook handles both the first installation and later updates, so keeping Discord current only takes one command.&lt;/p&gt;
&lt;p&gt;So, go ahead  -  run it, relax, and enjoy Fedora without the hassle. Because let's face it: managing updates manually is so last year.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Moving to Fedora: Automating PowerShell Installation with Ansible</title>
        <published>2024-11-15T00:00:00+00:00</published>
        <updated>2024-11-15T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/fedora-powershell-ansible/"/>
        <id>https://svnscha.de/posts/fedora-powershell-ansible/</id>
        <summary type="html">Because repeating manual steps on multiple machines isn't fun anymore.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/fedora-powershell-ansible/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;I use PowerShell on Fedora for scripting and automation. Installing it manually is fine once, but it becomes repetitive across workstations, VMs, dev containers, and servers.&lt;/p&gt;
&lt;p&gt;This Ansible playbook automates the installation and keeps the setup consistent across those environments.&lt;/p&gt;
&lt;h3 id=&quot;step-1-the-problem&quot;&gt;Step 1: The Problem&lt;/h3&gt;
&lt;p&gt;Installing PowerShell on Fedora involves downloading Microsoft's repository package, registering it, updating the package index, and then installing the actual PowerShell package. It's fine if you're doing it once, but if you have to repeat the process on multiple systems, it quickly becomes a chore.&lt;/p&gt;
&lt;p&gt;The solution? Use Ansible to do it for you. Write the playbook once, and it works everywhere  -  no matter how many machines you're dealing with.&lt;/p&gt;
&lt;h3 id=&quot;step-2-the-playbook&quot;&gt;Step 2: The Playbook&lt;/h3&gt;
&lt;p&gt;Here's the Ansible playbook that automates PowerShell installation on Fedora:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Install PowerShell on Fedora 41 using Ansible&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  hosts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  become&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  gather_facts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  tasks&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Download Microsoft repository package&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.get_url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        url&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;https://packages.microsoft.com/config/rhel/8/packages-microsoft-prod.rpm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        dest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/tmp/packages-microsoft-prod.rpm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Register Microsoft repository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.command&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        cmd&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;rpm -i /tmp/packages-microsoft-prod.rpm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      args&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        creates&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/etc/yum.repos.d/microsoft-prod.repo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Remove repository package after registration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.file&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/tmp/packages-microsoft-prod.rpm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;absent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Update package index&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.dnf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        update_cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Install PowerShell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.dnf&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;powershell&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        state&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;present&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Verify PowerShell installation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.command&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        cmd&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;pwsh --version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      register&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;pwsh_version_output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Display PowerShell version&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      ansible.builtin.debug&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        msg&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Installed PowerShell version: {{ pwsh_version_output.stdout }}&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-usage&quot;&gt;Step 3: Usage&lt;/h3&gt;
&lt;p&gt;Running this playbook is straightforward. If you're working with a virtual environment for Ansible, make sure it's activated. Then, execute the playbook locally with the following command:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ansible-playbook&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /path/to/install-powershell.yml&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --connection=local&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -K&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's a quick breakdown:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ansible-playbook&lt;/code&gt;: Runs the playbook.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/path/to/install-powershell.yml&lt;/code&gt;: Path to the playbook file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--connection=local&lt;/code&gt;: Executes the playbook on the local machine instead of a remote host.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-K&lt;/code&gt;: Prompts for sudo privileges to perform the installation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this command, you can easily install PowerShell on your Fedora system without worrying about missing steps.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;With this playbook, installing PowerShell on a new Fedora system takes one command and produces the same setup each time.&lt;/p&gt;
&lt;p&gt;So, the next time you're staring down the prospect of yet another manual PowerShell install, let Ansible take care of it. Because honestly, you've got better things to do.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Simplify SSH</title>
        <published>2024-10-03T00:00:00+00:00</published>
        <updated>2024-10-03T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/simplify-ssh/"/>
        <id>https://svnscha.de/posts/simplify-ssh/</id>
        <summary type="html">How to stop typing your SSH Key passphrase every time. (because, let's be real, we're all a little lazy)</summary>
        <content type="html" xml:base="https://svnscha.de/posts/simplify-ssh/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;I was tired of entering my SSH key passphrase every time I connected to a server. The setup below lets &lt;code&gt;ssh-agent&lt;/code&gt; remember it for the current terminal session and also keeps host settings in one place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's time-consuming and frustrating. I'm tired of it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;So, you've got your shiny new SSH key and every time you want to connect to a server, you're greeted with that ever-familiar password prompt. Sure, typing your SSH key passphrase every time is good for security, but, come on  -  who wants to do that? I mean, I don't know about you, but I've got enough passwords to remember without constantly dealing with this nonsense.&lt;/p&gt;
&lt;p&gt;Let's save ourselves some time and effort by getting the SSH agent to remember our keys for us. That way, you can just run your commands like the seasoned developer you are without worrying about the whole &quot;password entering&quot; thing every single time.&lt;/p&gt;
&lt;h2 id=&quot;step-1-start-the-ssh-agent&quot;&gt;Step 1: Start the SSH Agent&lt;/h2&gt;
&lt;p&gt;First things first  -  let's get the SSH agent up and running. It's like your personal SSH butler, here to remember your key so you don't have to.&lt;/p&gt;
&lt;p&gt;Open up your terminal and type:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;eval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; $(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ssh-agent&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Boom! Now the agent is running in the background, ready to hold your keys like the responsible little daemon it is.&lt;/p&gt;
&lt;h2 id=&quot;step-2-add-your-key-to-the-ssh-agent&quot;&gt;Step 2: Add your key to the SSH Agent&lt;/h2&gt;
&lt;p&gt;Now that the agent is up, let's give it your key. You only need to do this once per session, and the agent will keep it ready for you.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ssh-add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/.ssh/your_key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;your_key&lt;/code&gt; with the name of your private key file. Enter the passphrase once and &lt;code&gt;ssh-agent&lt;/code&gt; will remember it for the rest of the terminal session.&lt;/p&gt;
&lt;h2 id=&quot;step-3-configure-your-ssh-setup-to-be-even-lazier-i-mean-efficient&quot;&gt;Step 3: Configure your SSH setup to be even lazier (I mean efficient)&lt;/h2&gt;
&lt;p&gt;Okay, we've got the agent running and the key added, but we can take it one step further. Why not tell SSH exactly what key to use for which server, so you never have to worry about it picking the wrong one?&lt;/p&gt;
&lt;p&gt;To do this, we're going to set up a &lt;code&gt;~/.ssh/config&lt;/code&gt; file. If you don't have this file yet, don't worry  -  it's as easy as creating it:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;touch&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/.ssh/config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, crack open that file with your favorite text editor and set things up like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; your-server-alias&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    HostName&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; your.server.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    User&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; your-username&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    IdentityFile&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/.ssh/your_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;    IdentitiesOnly&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;breaking-it-down&quot;&gt;Breaking it down&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt;: You can use any alias here that makes sense to you. This is what you'll type when you want to connect to this server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HostName&lt;/strong&gt;: The actual domain or IP address of the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User&lt;/strong&gt;: Your username on that server, so you do not have to specify it each time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IdentityFile&lt;/strong&gt;: The path to your SSH private key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IdentitiesOnly yes&lt;/strong&gt;: Tells SSH to use &lt;em&gt;only&lt;/em&gt; this key, rather than trying every key it can find in the agent (which is how you avoid that annoying &quot;user mismatch&quot; issue).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-4-create-a-wrapper-script-for-ssh-agent-control&quot;&gt;Step 4: Create a wrapper script for SSH Agent control&lt;/h2&gt;
&lt;p&gt;Alright, you want control over when your SSH agent starts up  -  totally understandable! Instead of running the agent automatically on login (which might feel a bit &lt;em&gt;too&lt;/em&gt; autonomous), we'll create a neat little wrapper script. You can trigger it whenever you feel like starting the agent and adding your SSH key for the session.&lt;/p&gt;
&lt;p&gt;Here's how to set that up.&lt;/p&gt;
&lt;h3 id=&quot;create-the-wrapper-script&quot;&gt;Create the wrapper script&lt;/h3&gt;
&lt;p&gt;Let's create a script that starts the SSH agent, adds your key, and gives you control over when it runs. We'll call this script &lt;code&gt;init-ssh&lt;/code&gt;, and we'll stick it somewhere like &lt;code&gt;/usr/local/bin&lt;/code&gt; so it's available from anywhere in your terminal.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the script:&lt;/strong&gt;
Open your terminal and create the &lt;code&gt;init-ssh&lt;/code&gt; script:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; nano&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /usr/local/bin/init-ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add the script content:&lt;/strong&gt;
Paste the following content into the file:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;#!/bin/bash&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Start the SSH agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;eval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; $(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ssh-agent&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -s&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Add the key to the agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ssh-add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/.ssh/your_key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Optional: Display agent status for peace of mind&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ssh-add&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -l&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make the script executable:&lt;/strong&gt;
Give the script executable permissions:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; chmod&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; +x&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /usr/local/bin/init-ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;how-to-use-the-script&quot;&gt;How to use the script&lt;/h4&gt;
&lt;p&gt;Whenever you want to start your SSH agent for a session, just run:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; init-ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start the agent, add your key, and show you a list of the keys currently loaded in the agent. You've got full control  -  start the agent when you want, stop it when you're done, and enjoy a password-free SSH experience for the duration of that session.&lt;/p&gt;
&lt;h3 id=&quot;optional-stopping-the-ssh-agent&quot;&gt;Optional: Stopping the SSH Agent&lt;/h3&gt;
&lt;p&gt;If you want to stop the SSH agent after you're done with it, you can either let it die when you close your terminal or manually kill it with:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;eval&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; $(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ssh-agent&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -k&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That way, you get all the convenience of an SSH agent when you need it, without it running indefinitely in the background. You're now in complete control of your SSH setup, and you can keep things secure and efficient, exactly the way you like it.&lt;/p&gt;
&lt;p&gt;Happy SSH'ing!&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Simplify Elasticsearch and Kibana Installation</title>
        <published>2024-04-24T00:00:00+00:00</published>
        <updated>2024-04-24T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/simplify-elasticsearch-kibana/"/>
        <id>https://svnscha.de/posts/simplify-elasticsearch-kibana/</id>
        <summary type="html">Let me walk you through how I created a bash script that sets up Elasticsearch and Kibana on a single-node cluster with minimal fuss.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/simplify-elasticsearch-kibana/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Setting up Elasticsearch and Kibana involves the same steps each time: update the system, configure repositories, install packages, and apply the initial configuration. I wrote a script to automate that work and make the setup reusable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's time-consuming and frustrating. I'm tired of it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;automating-the-setup&quot;&gt;Automating the Setup&lt;/h2&gt;
&lt;p&gt;This Bash script installs Elasticsearch and Kibana as a single-node cluster. I use it when I need a small, repeatable setup without adding another configuration tool.&lt;/p&gt;
&lt;h2 id=&quot;why-choose-a-bash-script&quot;&gt;Why Choose a Bash Script?&lt;/h2&gt;
&lt;p&gt;I chose Bash instead of Ansible because the script also works well with cloud-init and inside Docker environments. It has no extra tooling requirements and is easy to copy to a new machine.&lt;/p&gt;
&lt;h2 id=&quot;building-the-script-a-step-by-step-guide&quot;&gt;Building the Script: A Step-by-Step Guide&lt;/h2&gt;
&lt;p&gt;Let's walk through the crafting of the script that automates setting up Elasticsearch and Kibana. Here's how I pieced it together, one step at a time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Checking for Superuser Privileges
The script needs administrative access, so it starts by checking for root privileges.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; [ &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -u&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; != &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ]; &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;   echo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;This script must be run as root&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; 1&amp;gt;&amp;amp;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;   exit&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Updating the System and Installing Essential Packages
Updating the system first helps avoid installation problems caused by outdated packages.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;apt&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;apt&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -y&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; apt-transport-https&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; gnupg&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; curl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; jq&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;Adding the Elasticsearch Repository
Getting this right is like picking the perfect ingredients for a master chef recipe - it ensures the rest of the meal turns out just right.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installing Elasticsearch and Kibana
Next, the script installs Elasticsearch and Kibana.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuring and Starting Services
The plot thickens! Setting up the services and getting them running is like reaching the climax of our story - where all elements come together to unveil the full potential of our setup.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rather than churn out another cookie-cutter guide on setting up Elasticsearch and Kibana, you can view the complete script &lt;a href=&quot;https://gist.github.com/svnscha/676291c9e1cdbfa261202b3897afba37&quot;&gt;here&lt;/a&gt;. However, I do want to emphasize the initial configuration process involving enrollment tokens and security settings. I've automated these aspects because, let's face it, I've read too many guides that suggest just turning off security for the sake of simplicity. That's not my style - I'd rather keep things secure automatically. It's not rocket science, but it sure is critical. Why simplify by compromising security when you can automate it effectively, right?&lt;/p&gt;
&lt;p&gt;This approach ensures you get the functionality you need without the hassle of manual setup or the risks of disabled security.&lt;/p&gt;
&lt;h2 id=&quot;perfecting-the-automation-securing-and-finalizing-the-setup&quot;&gt;Perfecting the Automation: Securing and Finalizing the Setup&lt;/h2&gt;
&lt;p&gt;The final part configures security and leaves both services ready to use.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Broadcasting the Good News
First things first, let's make sure any user logging into the system knows what's been accomplished:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;=== init-vm.sh: Initial ===&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/etc/motd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;[&amp;gt; Elasticsearch 'elastic' password: &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$ELASTIC_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/etc/motd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;[&amp;gt; Test instance with 'curl -k -X GET https://elastic:&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$ELASTIC_PASSWORD&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;@localhost:9200'&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/etc/motd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;=== init-vm.sh: Get started ===&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/etc/motd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -e&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;[&amp;gt; Reset 'elastic' password with '/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic'.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/etc/motd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we're updating the message of the day (MOTD) file to ensure anyone who logs in is immediately informed about how to interact with Elasticsearch and what steps to take next. It's like leaving a note on the fridge - impossible to ignore and incredibly helpful.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Decoding the Secrets
Automation isn't just about doing things without human intervention; it's also about doing them securely and wisely:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Because who likes doing setup manually, right?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;decoded_token&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $KIBANA_ENROLLMENT_TOKEN&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;base64&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --decode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $decoded_token&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '.adr[0]'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;fingerprint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $decoded_token&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '.fgr'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;api_key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $decoded_token&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '.key'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Decoding the enrollment token reveals the essential elements needed to securely configure Kibana: the address, fingerprint, and API key.&lt;/p&gt;
&lt;p&gt;Harnessing Version and Build Information
Knowing exactly which version and build of Kibana you're working with is required for the API, so let's get that:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ver&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '.version'&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /usr/share/kibana/package.json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;jq&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -r&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '.build.number'&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /usr/share/kibana/package.json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Re-encoding and Verification
Secrecy is paramount, and so is verification. Here's how we handle both:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;encoded_api_key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -n&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $api_key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;base64&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /usr/share/kibana/bin/kibana-verification-code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;verification_code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=$(&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; $output&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;awk&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -F&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;: &quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '{print $2}'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sed&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; 's/ //g'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API key needs to be re-encoded to maintain security, and we also extract a verification code necessary for the next step - enrolling Kibana.&lt;/p&gt;
&lt;h2 id=&quot;the-final-act-enrolling-kibana&quot;&gt;The Final Act: Enrolling Kibana&lt;/h2&gt;
&lt;p&gt;And now, the final piece of our automation puzzle:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -k&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -v&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -X&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; POST&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;http://localhost:5601/internal/interactive_setup/enroll&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Accept: */*&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Content-Type: application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Host: localhost:5601&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Origin: http://localhost:5601&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Referer: http://localhost:5601/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;kbn-build-number: &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$build&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;kbn-version: &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$ver&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;x-elastic-internal-origin: Kibana&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -H&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;x-kbn-context: %7B%22type%22%3A%22application%22%2C%22name%22%3A%22interactiveSetup%22%2C%22url%22%3A%22%2F%22%7D&quot;&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;     -d&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; '{&quot;hosts&quot;:[&quot;https://'&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$address&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'&quot;],&quot;apiKey&quot;:&quot;'&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$encoded_api_key&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'&quot;,&quot;caFingerprint&quot;:&quot;'&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$fingerprint&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'&quot;,&quot;code&quot;:&quot;'&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$verification_code&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'&quot;}'&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This curl command not only sends all the needed parameters to Kibana for configuration but also uses the verification code to ensure that the setup is both authorized and secure. It's akin to dotting the i's and crossing the t's in our setup script.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;The script turns a fresh system into a secured, working Elasticsearch and Kibana installation. Feel free to adapt it to your own environment.&lt;/p&gt;
&lt;p&gt;Check out the complete script here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/svnscha/676291c9e1cdbfa261202b3897afba37&quot;&gt;init-elastic-search-kibana-vm.sh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy automating, and here's to many insightful data explorations!&lt;/p&gt;
&lt;p&gt;P.S.: This script is tailored for automating development environments. Remember, a single-node cluster isn’t suited for production use, and storing passwords in the message of the day (MOTD) file? That’s a no-go for serious deployments. 😏&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Simplify Visual Studio Installation</title>
        <published>2024-02-12T00:00:00+00:00</published>
        <updated>2024-02-12T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/simplify-vs/"/>
        <id>https://svnscha.de/posts/simplify-vs/</id>
        <summary type="html">Quickly master Visual Studio installation with this easy guide. Ideal for developers seeking a hassle-free setup and efficient development environment.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/simplify-vs/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Every year, like clockwork, I find myself setting up my local dev environment from scratch. And every time, it's the same old song and dance: downloading, installing, configuring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's time-consuming and frustrating. I'm tired of it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;can-it-be-easier&quot;&gt;Can It Be Easier?&lt;/h2&gt;
&lt;p&gt;What if setting up a new machine, rolling it back, or tweaking it could be as simple as running a build on a server? What if I told you we could apply the same principles of automation to our local setups?&lt;/p&gt;
&lt;h2 id=&quot;lets-find-a-solution&quot;&gt;Let's Find A Solution&lt;/h2&gt;
&lt;p&gt;Creating a new development environment shouldn't be a chore. It should be quick, painless, and even a little bit fun. To turn this dream into reality, I need two things: the right software and the perfect setup. That's where &lt;code&gt;winget&lt;/code&gt; and &lt;code&gt;.vsconfig&lt;/code&gt; come in.&lt;/p&gt;
&lt;p&gt;With a list of essential software and some PowerShell tricks, I began to make boring tasks automatic. I picked each program, from 7Zip to Visual Studio, for its important use. But the adventure of automating everything is a story for another time.&lt;/p&gt;
&lt;h3 id=&quot;streamlining-visual-studio-installation&quot;&gt;Streamlining Visual Studio Installation&lt;/h3&gt;
&lt;p&gt;My main event? Automating Visual Studio installation. Using &lt;code&gt;winget&lt;/code&gt;, I can install Visual Studio with just a line:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;winget install -e --id Microsoft.VisualStudio.&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2022.&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Enterprise --silent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But I didn't stop there. With the &lt;code&gt;--override&lt;/code&gt; flag, I tailored the installation to include specific workloads, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;winget install -e --id Microsoft.VisualStudio.&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;2022.&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Enterprise --silent --override &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;--wait --quiet --addProductLang En-us --config C:\vs2022.vsconfig&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method sets up Visual Studio with the workload I need without having to manually select each package.&lt;/p&gt;
&lt;h4 id=&quot;whats-vsconfig&quot;&gt;What's &lt;code&gt;.vsconfig&lt;/code&gt;?&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;.vsconfig&lt;/code&gt; helps me easily replicate my development environment wherever I am. It's a JSON file that, with just a few clicks in the Visual Studio Installer, allows me to compress my environment workloads into a neat package. This approach is not only perfect for keeping &lt;strong&gt;build servers&lt;/strong&gt; and &lt;strong&gt;personal development environments in sync&lt;/strong&gt; with identical workloads, but it also simplifies sharing updates and changes with teammates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example: vs2022.vsconfig&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;version&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;components&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Roslyn.Compiler&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.Component.MSBuild&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Roslyn.LanguageServices&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.SQL.LocalDB.Runtime&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.CoreEditor&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Workload.CoreEditor&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.TypeScript.TSServer&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.JavaScript.TypeScript&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.TextTemplating&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.NuGet&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Debugger.JustInTime&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Component.Microsoft.VisualStudio.LiveShare.2022&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.IntelliCode&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.ClassDesigner&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.GraphDocument&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.CodeMap&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.CoreIde&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.Tools.x86.x64&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Graphics.Tools&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.DiagnosticTools&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Windows11SDK.22621&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.ATL&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.SecurityIssueAnalysis&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.ComponentGroup.ArchitectureTools.Native&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.Redist.14.Latest&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Windows11Sdk.WindowsPerformanceToolkit&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.CppBuildInsights&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions.CMake&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.CMake.Project&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.TestAdapterForBoostTest&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.ASAN&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.Vcpkg&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.Tools.ARM64EC&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Workload.NativeDesktop&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;Microsoft.VisualStudio.Component.VC.Runtimes.ARM64EC.Spectre&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can create a .vsconfig file with these easy steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Launch the Visual Studio Installer and choose to modify your setup.&lt;/li&gt;
&lt;li&gt;Hit More and then select the option to Export your settings into a .vsconfig file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's it!&lt;/p&gt;
&lt;h3 id=&quot;why-bother-with-all-this&quot;&gt;Why Bother With All This?&lt;/h3&gt;
&lt;p&gt;I'm all about making life easier, not harder. Thanks to &lt;code&gt;winget&lt;/code&gt; and Visual Studio's &lt;code&gt;.vsconfig&lt;/code&gt;, what used to eat up a lot of time now takes no time at all.&lt;/p&gt;
&lt;p&gt;Setting up a fresh development environment is now a piece of cake - and I mean the kind of delicious cake you can buy ready-made but tastes as good as anything you'd bake yourself. So here's to spending less time on setup and more on the fun stuff: coding.&lt;/p&gt;
</content>
    </entry>
</feed>
