<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>svnscha - ai</title>
    <subtitle>automating annoying tasks, sharing tips, and embracing less frustration</subtitle>
    <link rel="self" type="application/atom+xml" href="https://svnscha.de/tags/ai/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://svnscha.de"/>
    <generator uri="https://astro.build/">Astro</generator>
    <updated>2026-09-10T00:00:00+00:00</updated>
    <id>https://svnscha.de/tags/ai/atom.xml</id>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 1.3.0</title>
        <published>2026-09-10T00:00:00+00:00</published>
        <updated>2026-09-10T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-1-3-0-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-1-3-0-release/</id>
        <summary type="html">The MCP server, Claude Code skills, and crash-analysis agent are now separate plugins for use with your preferred installation.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-1-3-0-release/">&lt;p&gt;Version 1.3.0 of &lt;strong&gt;mcp-windbg&lt;/strong&gt; separates the Claude Code integration into three plugins. If you've already installed mcp-windbg yourself, you can now add the debugging skills and crash-analysis agent to that connection.&lt;/p&gt;
&lt;h2 id=&quot;three-separate-plugins&quot;&gt;Three Separate Plugins&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;/posts/mcp-windbg-1-2-0-release/&quot;&gt;1.2.0 release&lt;/a&gt; bundled the server, skills, and agent together. I split them so you can choose the parts you need:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;What it provides&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp-windbg-uvx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The MCP server, launched with uvx, and default symbol settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp-windbg-skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Four debugging skills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp-windbg-agents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;crash-analyst&lt;/code&gt; agent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The uvx plugin now ships only the server configuration. Skills and agents are independent: install either, both, or neither.&lt;/p&gt;
&lt;h2 id=&quot;skills-and-agents-for-your-existing-server&quot;&gt;Skills and Agents for Your Existing Server&lt;/h2&gt;
&lt;p&gt;If mcp-windbg is already registered in Claude Code, add the marketplace and install the workflows you want:&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin marketplace add svnscha/mcp-windbg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin install mcp-windbg-skills@mcp-windbg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin install mcp-windbg-agents@mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The skills cover the same four tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;analyze-dump&lt;/code&gt;&lt;/strong&gt; for crash dump analysis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;debug-remote&lt;/code&gt;&lt;/strong&gt; for live user-mode debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;kernel-debug&lt;/code&gt;&lt;/strong&gt; for live kernel debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;windbg-doctor&lt;/code&gt;&lt;/strong&gt; for checking the debugging setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They use your configured MCP connection, whether it runs a native executable, a Python installation, or an HTTP service. Setup diagnosis checks that server's actual launcher and host. It no longer assumes every installation needs uv.&lt;/p&gt;
&lt;p&gt;For 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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/mcp-windbg-skills:analyze-dump C:\dumps\app.dmp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent is available as &lt;code&gt;mcp-windbg-agents:crash-analyst&lt;/code&gt;. It investigates a dump and reports its likely cause, debugger evidence, and next steps. It uses the MCP tools directly and does not need the skills plugin.&lt;/p&gt;
&lt;p&gt;If you want the plugin to start the server through uvx, install it as well:&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin install mcp-windbg-uvx@mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows and CDB/KD are still required on the debugging host. The server's built-in MCP prompts are available independently of these plugins.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Enterprise environments:&lt;/strong&gt; Managed &lt;code&gt;allowedMcpServers&lt;/code&gt; settings can cause Claude Code to silently skip plugin-bundled MCP servers (&lt;a href=&quot;https://github.com/anthropics/claude-code/issues/32882&quot;&gt;issue #32882&lt;/a&gt;). I recommend &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/reference/clients/#registering-the-server-directly&quot;&gt;manual installation and registration&lt;/a&gt; plus the skills plugin, with the agents plugin if needed. The server must still be permitted by your organization's MCP policy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;upgrading-from-the-bundled-plugin&quot;&gt;Upgrading from the Bundled Plugin&lt;/h2&gt;
&lt;p&gt;Update the marketplace and server plugin:&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin marketplace update mcp-windbg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin update mcp-windbg-uvx@mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install the skills and agents plugins using the commands above if you want to keep those workflows.&lt;/p&gt;
&lt;p&gt;Skill commands now start with &lt;code&gt;/mcp-windbg-skills:&lt;/code&gt; instead of &lt;code&gt;/mcp-windbg:&lt;/code&gt;. For example, &lt;code&gt;/mcp-windbg:analyze-dump&lt;/code&gt; becomes &lt;code&gt;/mcp-windbg-skills:analyze-dump&lt;/code&gt;. References to the agent should use &lt;code&gt;mcp-windbg-agents:crash-analyst&lt;/code&gt;. Restart Claude Code if the new components do not appear.&lt;/p&gt;
&lt;p&gt;See the full &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/releases/tag/v1.3.0&quot;&gt;v1.3.0 release notes&lt;/a&gt; and the &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/reference/plugin/&quot;&gt;plugin guide&lt;/a&gt; for installation, switching servers, and updating each plugin.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 1.2.2</title>
        <published>2026-09-05T00:00:00+00:00</published>
        <updated>2026-09-05T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-1-2-2-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-1-2-2-release/</id>
        <summary type="html">A fix for missing Unicode output on Chinese, Japanese, and Korean Windows systems.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-1-2-2-release/">&lt;p&gt;Version 1.2.2 of &lt;strong&gt;mcp-windbg&lt;/strong&gt; is out. This release fixes missing Unicode output on Windows systems that use a multibyte code page, including Chinese, Japanese, and Korean systems.&lt;/p&gt;
&lt;p&gt;The bug was reported by a community member in &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/issues/102&quot;&gt;issue #102&lt;/a&gt;. Thanks for taking the time to provide a clear report and help track it down.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;du&lt;/code&gt; command, which displays a Unicode string, sometimes returned an empty result through mcp-windbg. Running the same command directly in CDB worked as expected. Other commands such as &lt;code&gt;db&lt;/code&gt; and &lt;code&gt;dw&lt;/code&gt; also worked against the same address.&lt;/p&gt;
&lt;p&gt;The important detail was the reporter's Chinese Windows system locale.&lt;/p&gt;
&lt;h2 id=&quot;what-caused-it&quot;&gt;What Caused It&lt;/h2&gt;
&lt;p&gt;mcp-windbg communicates with &lt;code&gt;cdb.exe&lt;/code&gt; through a pipe. It sends a command and reads the debugger's response from that pipe.&lt;/p&gt;
&lt;p&gt;On multibyte system code pages, such as Chinese 936, Japanese 932, Korean 949, or the &quot;Use Unicode UTF-8&quot; setting, CDB can truncate text while writing it to a pipe. The end of a line may be missing, including the closing quote and newline.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;du&lt;/code&gt;, this could remove the entire string. If the output was cut in the middle of a character, later commands could also time out because the session could no longer parse the incomplete output correctly.&lt;/p&gt;
&lt;p&gt;This only affects redirected output, which is why the command still looks correct when run in a normal debugger window. There is no CDB option that fixes the pipe output; interactive mode, batch mode, and ANSI log files all show the same problem.&lt;/p&gt;
&lt;h2 id=&quot;the-fix&quot;&gt;The Fix&lt;/h2&gt;
&lt;p&gt;CDB can write complete UTF-16 log files using &lt;code&gt;.logopen /u&lt;/code&gt;, so mcp-windbg now uses one on affected systems. The pipe is still used to manage command timing, but the command output itself is read from the UTF-16 log.&lt;/p&gt;
&lt;p&gt;This is not a special case for &lt;code&gt;du&lt;/code&gt;. It also fixes Unicode text in module paths, &lt;code&gt;!analyze&lt;/code&gt; output, string dumps, and other debugger commands. Systems with a single-byte code page continue to use the existing pipe-based output.&lt;/p&gt;
&lt;p&gt;I tested the fix with real crash dumps, a live kernel target, and remote sessions using the 1252, 936, and 65001 code pages.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Upgrade 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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -U&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use the Claude Code plugin, update the plugin to pick up the new version.&lt;/p&gt;
&lt;p&gt;The project is available on &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;GitHub&lt;/a&gt;. If you notice any other commands behaving differently on a non-English Windows system, please &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/issues&quot;&gt;open an issue&lt;/a&gt;.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 1.2.0</title>
        <published>2026-08-27T00:00:00+00:00</published>
        <updated>2026-08-27T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-1-2-0-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-1-2-0-release/</id>
        <summary type="html">mcp-windbg now ships as a Claude Code plugin with four debugging skills and a crash-analysis agent.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-1-2-0-release/">&lt;p&gt;Version 1.2.0 of &lt;strong&gt;mcp-windbg&lt;/strong&gt; adds a Claude Code plugin. It packages the MCP server together with the instructions and workflows needed for common debugging tasks, so Claude Code can install and use it without a separate Python setup or a hand-written MCP configuration.&lt;/p&gt;
&lt;h2 id=&quot;installing-the-plugin&quot;&gt;Installing the Plugin&lt;/h2&gt;
&lt;p&gt;Add the mcp-windbg marketplace and install the plugin from Claude Code:&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;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin marketplace add svnscha/mcp-windbg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/plugin install mcp-windbg-uvx@mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plugin starts mcp-windbg with &lt;code&gt;uvx&lt;/code&gt;, which downloads the pinned version from PyPI the first time it runs. You still need Windows, CDB, and &lt;code&gt;uv&lt;/code&gt;, but you do not need to install the Python package yourself.&lt;/p&gt;
&lt;p&gt;The existing &lt;code&gt;pip install mcp-windbg&lt;/code&gt; setup remains available for other MCP clients and for anyone who prefers a manual configuration.&lt;/p&gt;
&lt;h2 id=&quot;skills-for-common-debugging-tasks&quot;&gt;Skills for Common Debugging Tasks&lt;/h2&gt;
&lt;p&gt;The plugin includes four skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;analyze-dump&lt;/code&gt;&lt;/strong&gt; guides the analysis of a Windows crash dump.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;debug-remote&lt;/code&gt;&lt;/strong&gt; works with a live user-mode target through CDB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;kernel-debug&lt;/code&gt;&lt;/strong&gt; handles live kernel debugging with KD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;windbg-doctor&lt;/code&gt;&lt;/strong&gt; checks the local setup when CDB, KD, symbols, or the MCP server are not working as expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These skills only load when they are needed, which keeps the plugin's normal context use small. The underlying MCP tools are still available directly when you want to run a specific command or control a session yourself.&lt;/p&gt;
&lt;h2 id=&quot;the-crash-analyst-agent&quot;&gt;The Crash Analyst Agent&lt;/h2&gt;
&lt;p&gt;The plugin also includes a read-only &lt;strong&gt;&lt;code&gt;crash-analyst&lt;/code&gt;&lt;/strong&gt; agent. It can investigate a dump independently and return:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;its likely cause&lt;/li&gt;
&lt;li&gt;the debugger evidence supporting that conclusion&lt;/li&gt;
&lt;li&gt;alternative causes it checked&lt;/li&gt;
&lt;li&gt;practical next steps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The agent is kept away from mutating tools. It can inspect a debugging session, but it cannot resume a target or perform another action that would change its state.&lt;/p&gt;
&lt;h2 id=&quot;symbols-by-default&quot;&gt;Symbols by Default&lt;/h2&gt;
&lt;p&gt;Readable stack traces depend on symbols, and missing symbol configuration is a common source of poor results. The plugin now provides the Microsoft symbol server as the default value for &lt;code&gt;_NT_SYMBOL_PATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you already have &lt;code&gt;_NT_SYMBOL_PATH&lt;/code&gt; configured, the plugin leaves it unchanged. Existing private symbol servers and custom cache paths therefore continue to work.&lt;/p&gt;
&lt;h2 id=&quot;a-small-121-follow-up&quot;&gt;A Small 1.2.1 Follow-up&lt;/h2&gt;
&lt;p&gt;Version 1.2.1 followed later the same day. It reorganized the README installation guide by client and fixed a corrupted &lt;code&gt;C:\dumps\app.dmp&lt;/code&gt; example that contained a hidden control character. There were no runtime changes, so it does not need a separate post.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Use the two plugin commands above for Claude Code, or upgrade the Python package for another MCP client:&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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -U&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the full release notes for &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/releases/tag/v1.2.0&quot;&gt;v1.2.0&lt;/a&gt; and &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/releases/tag/v1.2.1&quot;&gt;v1.2.1&lt;/a&gt;. The source and plugin marketplace are available in the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;mcp-windbg repository&lt;/a&gt;.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 1.1.0</title>
        <published>2026-08-26T00:00:00+00:00</published>
        <updated>2026-08-26T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-1-1-0-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-1-1-0-release/</id>
        <summary type="html">Support for the 2.x MCP SDK, a new wait_for_break tool, and fixes for 'g' freezing the machine it was supposed to release.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-1-1-0-release/">&lt;p&gt;Version 1.1.0 of &lt;strong&gt;mcp-windbg&lt;/strong&gt; is out. It's the first release since &lt;a href=&quot;/posts/mcp-windbg-1-0-0-release/&quot;&gt;1.0.0&lt;/a&gt;, which was broken on fresh installs for some time.&lt;/p&gt;
&lt;h2 id=&quot;what-happened-101&quot;&gt;What Happened: 1.0.1&lt;/h2&gt;
&lt;p&gt;The dependency was &lt;code&gt;mcp&amp;gt;=1.28.1&lt;/code&gt; with no upper bound. When the MCP Python SDK shipped 2.0.0 - renaming &lt;code&gt;McpError&lt;/code&gt; to &lt;code&gt;MCPError&lt;/code&gt; and dropping the &lt;code&gt;@server.list_tools()&lt;/code&gt; decorator - every fresh install broke at import:&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;ImportError: cannot import name 'McpError' from 'mcp.shared.exceptions'&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Existing installs worked fine; only new ones broke. That's the worst kind of bug: invisible to anyone who could report it. 1.0.1 capped the requirement at &lt;code&gt;&amp;lt;2.0.0&lt;/code&gt;. Thanks to @aphroteus for the fix and @arjunarjun07 for finding the cause.&lt;/p&gt;
&lt;p&gt;If you're still on 1.0.0, run &lt;code&gt;pip install --upgrade mcp-windbg&lt;/code&gt;. Any &lt;code&gt;pip install &quot;mcp&amp;lt;2&quot;&lt;/code&gt; workaround can go.&lt;/p&gt;
&lt;h2 id=&quot;support-for-the-2x-sdk&quot;&gt;Support for the 2.x SDK&lt;/h2&gt;
&lt;p&gt;A cap is a stopgap, not a fix. 1.1.0 runs on &lt;code&gt;mcp&amp;gt;=2.0.0&lt;/code&gt;: handlers are passed straight to &lt;code&gt;Server(...)&lt;/code&gt; instead of registered via decorators, and they return the SDK's result types (&lt;code&gt;ListToolsResult&lt;/code&gt;, &lt;code&gt;CallToolResult&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Nothing changes for clients - same tools, same schemas, stdio and streamable-http both work as before. Python support is unchanged (the 2.x SDK wants 3.10+, like this project already did).&lt;/p&gt;
&lt;p&gt;Two changes so the next SDK release is boring instead of another five silent weeks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All runtime dependencies are capped at the next major version.&lt;/strong&gt; Without a ceiling, moving to 2.x would have left the same trap for 3.0.0. Since mcp-windbg is an application, not a library, upper bounds can't conflict with anything downstream - a breaking SDK release just becomes a failing CI run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A weekly canary tests what users actually get.&lt;/strong&gt; CI installed from &lt;code&gt;uv.lock&lt;/code&gt;, so it only tested pinned versions, while real installs resolve whatever the ranges allow. The canary ignores the lock, installs the newest allowed versions, and runs the test suite every Monday. It's scheduled rather than tied to pull requests on purpose: an upstream break should page me, not block someone's unrelated PR.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;wait_for_break&quot;&gt;&lt;code&gt;wait_for_break&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The one new tool. Resume a target with &lt;code&gt;g&lt;/code&gt;, go do something else, then call &lt;code&gt;wait_for_break&lt;/code&gt; to block until it stops again - breakpoint, bugcheck, or CTRL+BREAK. It returns everything the debugger printed along the way.&lt;/p&gt;
&lt;p&gt;If the wait expires, the target keeps running. Waiting never halts your machine behind your back.&lt;/p&gt;
&lt;h2 id=&quot;fixing-g-freezing-the-target&quot;&gt;Fixing &lt;code&gt;g&lt;/code&gt; Freezing the Target&lt;/h2&gt;
&lt;p&gt;This was bad and hid for a while, so let me be honest about it.&lt;/p&gt;
&lt;p&gt;Go-class commands (&lt;code&gt;g&lt;/code&gt;, &lt;code&gt;gh&lt;/code&gt;, &lt;code&gt;gn&lt;/code&gt;, &lt;code&gt;gN&lt;/code&gt;, &lt;code&gt;gc&lt;/code&gt;, &lt;code&gt;gu&lt;/code&gt;) hand control back to the target, after which the debugger stops reading stdin. The server queued an &lt;code&gt;.echo&lt;/code&gt; marker behind the &lt;code&gt;g&lt;/code&gt;, which never got answered - so the command sat until timeout, and the timeout's CTRL+BREAK halted the target again. Ask the machine to run, get it frozen a minute later. The opposite of what you asked for.&lt;/p&gt;
&lt;p&gt;Go-class commands are now sent bare and return immediately. Step commands (&lt;code&gt;p&lt;/code&gt;, &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;pa&lt;/code&gt;, &lt;code&gt;ta&lt;/code&gt;, ...) are unaffected.&lt;/p&gt;
&lt;p&gt;Fixing that exposed several related problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A command right after &lt;code&gt;g&lt;/code&gt; breaks in cleanly.&lt;/strong&gt; No manual &lt;code&gt;send_ctrl_break&lt;/code&gt;, and the target's output isn't thrown away.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A break-in sent immediately after &lt;code&gt;g&lt;/code&gt; no longer gets lost.&lt;/strong&gt; Resuming used to return before the debugger read the &lt;code&gt;g&lt;/code&gt;, leaving a window where a CTRL+BREAK hit the prompt instead of the target. Against a live KDNET target, a break sent with no gap was lost &lt;em&gt;every single time&lt;/em&gt;. The resume is now confirmed consumed before reporting success.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Break-in checks before signaling.&lt;/strong&gt; A CTRL+BREAK aimed at an already-stopped target would queue and re-halt it later. The session now probes for a prompt first and only signals if there's no answer, making speculative &lt;code&gt;send_ctrl_break&lt;/code&gt; calls safe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;bp nt!X; g&lt;/code&gt; reports whether the breakpoint was set.&lt;/strong&gt; A typo'd symbol now shows up as &lt;code&gt;Couldn't resolve error&lt;/code&gt; instead of an endless wait.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One operation per session at a time.&lt;/strong&gt; &lt;code&gt;wait_for_break&lt;/code&gt; parks on a worker thread while the server keeps answering other requests; a second call on the same session is refused immediately. Closing a session also ends any wait parked on it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kernel sessions over named pipe or serial connect.&lt;/strong&gt; &lt;code&gt;kd&lt;/code&gt; announces those links with a different message than KDNET, and only the latter was matched - so sessions that were actually attached timed out.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Still one 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;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; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Release notes: &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/releases/tag/v1.1.0&quot;&gt;v1.1.0&lt;/a&gt; - Docs: &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/&quot;&gt;svnscha.github.io/mcp-windbg&lt;/a&gt; - Source: &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;github.com/svnscha/mcp-windbg&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;No headline features here. Just a dependency cap, a canary, one new tool, and a long list of places where the debugger and the server disagreed about who was talking. That's what a &lt;code&gt;.1&lt;/code&gt; release should be - especially after a 1.0.0 nobody could install.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If mcp-windbg has helped you, I'd love to hear about it. Open an issue, reach out, or star the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;repository&lt;/a&gt;. Thank you.&lt;/em&gt;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 1.0.0</title>
        <published>2026-07-16T00:00:00+00:00</published>
        <updated>2026-07-16T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-1-0-0-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-1-0-0-release/</id>
        <summary type="html">Kernel debugging, session IDs, and a redesigned tool surface. The first stable release.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-1-0-0-release/">&lt;p&gt;I've just released version 1.0.0 of &lt;strong&gt;mcp-windbg&lt;/strong&gt;. Yes, one-point-zero. The headline: &lt;strong&gt;your AI assistant can now debug the Windows kernel.&lt;/strong&gt; The fine print: every tool has a new name, so bring your prompts. Both below.&lt;/p&gt;
&lt;h2 id=&quot;kernel-debugging&quot;&gt;Kernel Debugging&lt;/h2&gt;
&lt;p&gt;Three new tools, &lt;code&gt;open_kd_session&lt;/code&gt;, &lt;code&gt;run_kd_command&lt;/code&gt;, and &lt;code&gt;close_kd_session&lt;/code&gt;, drive &lt;code&gt;kd.exe&lt;/code&gt; against a live kernel target over the connections you would actually use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KDNET&lt;/strong&gt; (&lt;code&gt;net:port=...,key=...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Named pipes&lt;/strong&gt; (&lt;code&gt;com:pipe,port=\\.\pipe\...&lt;/code&gt;) - the classic VM setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serial&lt;/strong&gt; - because sometimes it is 2003 in your lab and that is fine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Point your assistant at a VM, ask what that driver is up to, and let it run &lt;code&gt;!process&lt;/code&gt;, &lt;code&gt;!irql&lt;/code&gt;, &lt;code&gt;lm&lt;/code&gt;, and friends while you have your coffee.&lt;/p&gt;
&lt;p&gt;A kernel target is not a dump file: break in at the wrong moment and you have halted an entire machine. The tools are built around that reality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sessions arrive already stopped.&lt;/strong&gt; &lt;code&gt;open_kd_session&lt;/code&gt; waits for the connect banner and breaks in for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Closing resumes the target.&lt;/strong&gt; &lt;code&gt;close_kd_session&lt;/code&gt; sends &lt;code&gt;g&lt;/code&gt; by default; pass &lt;code&gt;resume: false&lt;/code&gt; only if you want the machine frozen on purpose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;--kd-path&lt;/code&gt;&lt;/strong&gt; points the server at a specific &lt;code&gt;kd.exe&lt;/code&gt;, the counterpart to &lt;code&gt;--cdb-path&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also two new guided prompts to go with it: &lt;strong&gt;&lt;code&gt;kernel-triage&lt;/code&gt;&lt;/strong&gt; investigates a kernel target (including telling a real bugcheck apart from a plain break-in, and releasing the machine at the end), and &lt;strong&gt;&lt;code&gt;remote-triage&lt;/code&gt;&lt;/strong&gt; does the same for a live user-mode process.&lt;/p&gt;
&lt;h2 id=&quot;session-ids&quot;&gt;Session IDs&lt;/h2&gt;
&lt;p&gt;The other big change: sessions are now first-class. Every &lt;code&gt;open_*&lt;/code&gt; tool returns an opaque &lt;strong&gt;session ID&lt;/strong&gt; (&lt;code&gt;cdb-…&lt;/code&gt; or &lt;code&gt;kd-…&lt;/code&gt;), and &lt;code&gt;run_*&lt;/code&gt;, &lt;code&gt;close_*&lt;/code&gt;, and &lt;code&gt;send_ctrl_break&lt;/code&gt; address a session by that ID. No more implicit sessions, no more addressing by dump path: open first, then talk. Opening the same dump twice gives you two independent sessions.&lt;/p&gt;
&lt;p&gt;The kind is enforced, too: pass a &lt;code&gt;cdb&lt;/code&gt; ID to &lt;code&gt;run_kd_command&lt;/code&gt; and you get an error naming the right tool. That matters more than it sounds when the caller is a model that occasionally guesses.&lt;/p&gt;
&lt;h2 id=&quot;the-breaking-changes&quot;&gt;The Breaking Changes&lt;/h2&gt;
&lt;p&gt;The old names conflated the product (WinDbg) with the engines doing the work (&lt;code&gt;cdb.exe&lt;/code&gt; for user mode, &lt;code&gt;kd.exe&lt;/code&gt; for kernel). That distinction is now load-bearing, so everything got renamed:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;0.x&lt;/th&gt;
&lt;th&gt;1.0.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list_windbg_dumps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list_dumps&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;open_windbg_dump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;open_cdb_dump&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;open_windbg_remote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;open_cdb_remote&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_windbg_cmd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;run_cdb_command&lt;/code&gt; / &lt;code&gt;run_kd_command&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;close_windbg_dump&lt;/code&gt;, &lt;code&gt;close_windbg_remote&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;close_cdb_session&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Also: &lt;code&gt;send_ctrl_break&lt;/code&gt; takes a &lt;code&gt;session_id&lt;/code&gt;, and the &lt;code&gt;connection_type&lt;/code&gt; parameter is gone because the tool name now says which engine you mean. If you have prompts or agent instructions written against the 0.x names, they need updating - that is exactly why this is 1.0.0 and not 0.16.0. The &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/&quot;&gt;docs&lt;/a&gt; are rewritten around the new flow, including a &quot;Debug a kernel target&quot; guide.&lt;/p&gt;
&lt;h2 id=&quot;smaller-but-you-will-notice&quot;&gt;Smaller, But You Will Notice&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-call timeouts&lt;/strong&gt;: &lt;code&gt;timeout_seconds&lt;/code&gt; on any &lt;code&gt;open_*&lt;/code&gt; / &lt;code&gt;run_*&lt;/code&gt; overrides that tool's default. Useful when that one &lt;code&gt;!analyze -v&lt;/code&gt; against a 30 GB dump needs its time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A slow command no longer wedges the session.&lt;/strong&gt; On timeout the server breaks in with CTRL+BREAK and resynchronizes, so the session is usable for the next command instead of stranded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output goes to the right command.&lt;/strong&gt; Every command waits on its own unique completion marker, so late output from a slow command can never be mistaken for the next one's result.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No more orphaned debuggers.&lt;/strong&gt; &lt;code&gt;cdb.exe&lt;/code&gt; launched via the Microsoft Store aliases spawns a child that a plain terminate left behind, still holding the target. Shutdown now kills the whole process tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Still a one-liner:&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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full release notes are on the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/releases/tag/v1.0.0&quot;&gt;v1.0.0 release page&lt;/a&gt;, and the project lives at &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;github.com/svnscha/mcp-windbg&lt;/a&gt;. A star is always appreciated.&lt;/p&gt;
&lt;h2 id=&quot;one-point-zero&quot;&gt;One-Point-Zero&lt;/h2&gt;
&lt;p&gt;Fourteen months ago this was a &lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;weekend experiment&lt;/a&gt;. Today it sits at 1.4k stars and debugs kernels. Calling it 1.0 is a promise more than a milestone: the names are stable now, and breaking changes from here on will be rare and loud.&lt;/p&gt;
&lt;p&gt;Here is to stable.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If mcp-windbg has helped you, I would love to hear about it. Reach out, open an issue, or just star the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;repository&lt;/a&gt;. Thank you all.&lt;/em&gt;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 0.15.0</title>
        <published>2026-06-09T00:00:00+00:00</published>
        <updated>2026-06-09T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-0-15-0-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-0-15-0-release/</id>
        <summary type="html">Break into live sessions, redact sensitive output before it reaches the model, and a brand-new documentation site. A year and a month in, with 1.4k stars and a community that keeps showing up.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-0-15-0-release/">&lt;p&gt;I've just released version 0.15.0 of &lt;strong&gt;mcp-windbg&lt;/strong&gt;. It has been a while since the &lt;a href=&quot;/posts/mcp-windbg-0-12-2-release/&quot;&gt;0.12.2 post&lt;/a&gt;, and three releases have landed since then. Rather than a single feature, this is a round-up of everything that has shipped: a way to break into a running session, a hook to scrub sensitive data before it ever reaches the model, and a proper documentation site to tie it all together.&lt;/p&gt;
&lt;h2 id=&quot;whats-new&quot;&gt;What's New&lt;/h2&gt;
&lt;h3 id=&quot;break-into-a-live-session&quot;&gt;Break into a live session&lt;/h3&gt;
&lt;p&gt;If you have used a real debugger, you know the moment: the process is spinning, you want to know what it is doing &lt;em&gt;right now&lt;/em&gt;, and you reach for CTRL+BREAK. That now works through the MCP interface too.&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;send_ctrl_break&lt;/code&gt; tool, contributed by &lt;a href=&quot;https://github.com/GeoYS&quot;&gt;@GeoYS&lt;/a&gt;, interrupts an active CDB/WinDbg session, for both dump and remote debugging workflows. So when you are driving a live target and it wanders off into a tight loop or a long wait, the model can break in, inspect the state, and carry on, the same way you would at the keyboard. This closes a real gap: before, a live session that was busy was a session you could only wait on.&lt;/p&gt;
&lt;h3 id=&quot;redact-sensitive-data-before-it-leaves-the-machine&quot;&gt;Redact sensitive data before it leaves the machine&lt;/h3&gt;
&lt;p&gt;Crash dumps are full of things you might not want to hand to a model: file paths with usernames, machine names, environment variables, command lines, sometimes worse. The new &lt;code&gt;--filter-script&lt;/code&gt; option lets you point the server at a trusted Python helper that can rewrite string-valued tool arguments and tool text output on the way through.&lt;/p&gt;
&lt;p&gt;The important part is the boundary. Your script only sees the string values, not the full MCP protocol messages, so you can do focused PII redaction (mask a username, strip a token, normalize a path) without writing a protocol-aware proxy. If you run a centralized analysis service, this is the piece that lets you control what actually crosses the wire.&lt;/p&gt;
&lt;h3 id=&quot;a-real-documentation-site&quot;&gt;A real documentation site&lt;/h3&gt;
&lt;p&gt;The project has outgrown a README and a wiki. There is now a proper &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/&quot;&gt;documentation site&lt;/a&gt; built with MkDocs Material and deployed to GitHub Pages. It covers getting started, the individual use cases, and a reference for the command-line options, the tools, and client configuration.&lt;/p&gt;
&lt;p&gt;A few highlights worth calling out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use-case guides&lt;/strong&gt; for the things people kept asking about: running the server over HTTP to &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/scenarios/http-service/&quot;&gt;debug from another machine&lt;/a&gt;, and scrubbing tool output to &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/scenarios/redaction/&quot;&gt;redact sensitive data&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The built-in &lt;code&gt;dump-triage&lt;/code&gt; prompt&lt;/strong&gt; is now &lt;a href=&quot;https://svnscha.github.io/mcp-windbg/reference/prompts/&quot;&gt;documented&lt;/a&gt;, including its &lt;code&gt;dump_path&lt;/code&gt; argument, so you can trigger a full structured triage without hand-writing the workflow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Honest limitations&lt;/strong&gt; are written down rather than discovered the hard way: the HTTP transport has no authentication, attach-by-PID is not supported, and sessions run concurrently.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;quieter-sturdier-foundations&quot;&gt;Quieter, sturdier foundations&lt;/h3&gt;
&lt;p&gt;This release also includes several smaller changes that matter in daily use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;stdio transport no longer crashes on a malformed input line&lt;/strong&gt;. A single unparseable line used to tear down the whole process; now it is logged and the server keeps running.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;test suite was rebuilt&lt;/strong&gt; as a declarative end-to-end harness. Every scenario runs against a really-hosted &lt;code&gt;python -m mcp_windbg&lt;/code&gt; server driven by a real MCP client, with only the LLM faked. Coverage now sits above 90 percent, measured in the subprocess where tool dispatch actually runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Package metadata is complete&lt;/strong&gt;, so &lt;code&gt;pip show&lt;/code&gt; and the PyPI page now carry the author, project URLs, and proper classifiers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Symbols resolve more often out of the box&lt;/strong&gt;: the dump's own directory is now auto-included in the symbol search path, so PDBs sitting next to a dump are found without extra configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;community-contributions&quot;&gt;Community Contributions&lt;/h2&gt;
&lt;p&gt;This stretch of releases leaned on the community, and two contributions stand out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GeoYS&quot;&gt;@GeoYS&lt;/a&gt; added the live break-in feature in &lt;a href=&quot;https://github.com/svnscha/mcp-windbg/pull/40&quot;&gt;#40&lt;/a&gt;, which became the &lt;code&gt;send_ctrl_break&lt;/code&gt; tool above. It is the headline of the 0.13.0 release.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/SSyl&quot;&gt;@SSyl&lt;/a&gt; made symbols just work in more cases by auto-including the dump directory in the symbol search path (&lt;a href=&quot;https://github.com/svnscha/mcp-windbg/pull/41&quot;&gt;#41&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is exactly the kind of contribution that makes a tool better for everyone, not just the person who filed it. Thank you both.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Installation is unchanged and still a one-liner:&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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The project is open source and available on GitHub at &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;https://github.com/svnscha/mcp-windbg&lt;/a&gt;. If you find it useful, a star on the repository is always appreciated.&lt;/p&gt;
&lt;h2 id=&quot;a-year-and-a-month-in&quot;&gt;A Year and a Month In&lt;/h2&gt;
&lt;p&gt;It is hard to believe, but mcp-windbg is now a year and a month old. When I &lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;first wrote about it&lt;/a&gt; in May 2025, it was a weekend experiment to scratch my own itch: I was tired of typing the same WinDbg commands over and over, and I wanted to see whether a model could drive the debugger for me. I genuinely did not expect it to go anywhere.&lt;/p&gt;
&lt;p&gt;It went somewhere. The repository now sits at &lt;strong&gt;1.4k stars&lt;/strong&gt;, the original article reached tens of thousands of readers in its first days, and the project has shown up in conversations, talks, and threads far beyond anything I planned. People have used it to triage real production crashes, wired it into team workflows, and sent screenshots of it cracking bugs that had been open for weeks. Every one of those still makes my day.&lt;/p&gt;
&lt;p&gt;What has meant the most, though, is not the numbers. It is the people. Contributors who saw a gap and just fixed it. Folks who filed thoughtful issues, asked sharp questions, or pushed back when something did not work the way they needed. The many of you who reached out directly, by email, on LinkedIn, in DMs, to say the tool saved you an afternoon, or to suggest where it should go next. A project like this is only as good as the community around it, and this one has been generous well beyond what I could have hoped for.&lt;/p&gt;
&lt;p&gt;So this is mostly a thank-you note. Thank you for the stars, the pull requests, the issues, the messages, and for trusting a small open-source tool with your hardest crashes. You are the reason it keeps getting better, and the reason I keep shipping.&lt;/p&gt;
&lt;p&gt;Here is to the next year.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If mcp-windbg has helped you, I would love to hear about it. Reach out, open an issue, or just star the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;repository&lt;/a&gt;. Thank you all.&lt;/em&gt;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Meet Ralph: The AI Coding Agent That Just Keeps Going</title>
        <published>2026-01-26T00:00:00+00:00</published>
        <updated>2026-01-26T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/meet-ralph/"/>
        <id>https://svnscha.de/posts/meet-ralph/</id>
        <summary type="html">I built an autonomous AI agent that works from a PRD, verifies its own work, and ships code while I sleep. Here's Ralph.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/meet-ralph/">&lt;p&gt;📺 &lt;strong&gt;Watch Ralph in Action:&lt;/strong&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=dfZb2LPdkMk&quot;&gt;Ralph implementing its own features&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🔗 &lt;strong&gt;Links:&lt;/strong&gt; &lt;a href=&quot;https://github.com/svnscha/ralph&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Shoutout to &lt;a href=&quot;https://x.com/mattpocockuk&quot;&gt;Matt Pocock&lt;/a&gt; whose &lt;a href=&quot;https://x.com/mattpocockuk/status/2007924876548637089&quot;&gt;viral tweet about &quot;Ralph Wiggum&quot;&lt;/a&gt; sparked this whole thing.&lt;/p&gt;
&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Because AI coding assistants have a dirty little secret: they forget.&lt;/p&gt;
&lt;p&gt;You start strong. &quot;Refactor this auth module.&quot; The AI gets it. Code looks good. You're feeling productive.&lt;/p&gt;
&lt;p&gt;Then 15 minutes later... it forgets what you discussed. It starts solving problems you never asked about. It rewrites code you specifically said to leave alone.&lt;/p&gt;
&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Most AI coding tools are built for short bursts. Quick questions, small fixes, code snippets. Try to run them for hours on a bigger task? Good luck. Context degrades. Focus drifts. You spend more time course-correcting than coding.&lt;/p&gt;
&lt;p&gt;I always wanted something different. An AI that could work autonomously on well-defined tasks. Start it before bed, wake up to working code. The missing piece? An agent that you can programmatically control.&lt;/p&gt;
&lt;p&gt;Now, with the GitHub Copilot SDK and the Ralph loop, this has become too easy not to do.&lt;/p&gt;
&lt;p&gt;So I built yet another Ralph.&lt;/p&gt;
&lt;h2 id=&quot;what-is-ralph&quot;&gt;What Is Ralph?&lt;/h2&gt;
&lt;p&gt;Ralph is a .NET CLI tool that treats AI as an autonomous agent, not a chat buddy. The key insight: &lt;strong&gt;structure breeds autonomy.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You describe what you want.&lt;/strong&gt; Ralph asks clarifying questions, then generates a structured PRD with prioritized user stories.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ralph executes autonomously.&lt;/strong&gt; Picks up one story at a time, implements it, runs verification scripts to confirm it works, commits, moves on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repeat until done.&lt;/strong&gt; You come back to find working, tested code.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;the-secret-sauce-the-prd&quot;&gt;The Secret Sauce: The PRD&lt;/h2&gt;
&lt;p&gt;At the heart of Ralph is a simple JSON file - the Product Requirements Document:&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;project&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;my-app&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;taskName&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;add-auth&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;userStories&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:#9CDCFE&quot;&gt;      &quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;US-001&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;title&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Create login endpoint&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;acceptanceCriteria&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;POST /api/auth/login accepts email and password&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;Returns JWT token on success&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;ralph_verify(Build) passes&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;ralph_verify(Test) passes&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:#9CDCFE&quot;&gt;      &quot;priority&quot;&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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;passes&quot;&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:#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;Each story is small enough to complete in one go. Ralph picks the next one where &lt;code&gt;passes: false&lt;/code&gt;, implements it, runs verification, and marks it complete. If the build fails, it sees the errors and fixes them. If tests fail, same thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The magic is in the &lt;code&gt;ralph_verify()&lt;/code&gt; calls.&lt;/strong&gt; These run actual C# verification scripts - compiled at runtime with Roslyn - that confirm the work is correct before moving on.&lt;/p&gt;
&lt;h2 id=&quot;verification-scripts&quot;&gt;Verification Scripts&lt;/h2&gt;
&lt;p&gt;Traditional AI assistants produce code and hope for the best. Ralph takes a different approach: &lt;strong&gt;every story must pass verification before it's marked complete&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ralph_verify()&lt;/code&gt; function in acceptance criteria tells Ralph which scripts to run. When Ralph sees &lt;code&gt;ralph_verify(Build) passes&lt;/code&gt;, it executes the Build script and confirms it succeeds before moving on.&lt;/p&gt;
&lt;p&gt;Ralph ships with Build and Test scripts out of the box. They're C# classes compiled at runtime with Roslyn:&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;csharp&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; Ralph&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Core&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Abstractions&lt;/span&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;namespace&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Ralph&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Scripts&lt;/span&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:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;VerificationScript&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Build&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Verifies solution compiles&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;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IVerificationScript&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:#4EC9B0&quot;&gt; VerificationResult&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Execute&lt;/span&gt;&lt;span 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:#6A9955&quot;&gt;        // Spawn dotnet build, capture output, return pass/fail&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 key insight: &lt;strong&gt;Ralph loops until all verification scripts pass&lt;/strong&gt;. If Build fails, Ralph sees the compiler errors and attempts to fix them. If Test fails, Ralph sees which tests failed and why. This self-correcting loop is what enables hours of autonomous work.&lt;/p&gt;
&lt;p&gt;Why custom scripts instead of just telling the AI &quot;run &lt;code&gt;dotnet build&lt;/code&gt;&quot;? Context efficiency. A successful build returns &quot;Build succeeded&quot; - not the entire compiler output. Only on failure does Ralph see the errors it needs to fix. This keeps the context window lean and focused on what matters.&lt;/p&gt;
&lt;p&gt;Want custom verification? Drop a script in &lt;code&gt;.ralph/scripts/&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;csharp&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:#4EC9B0&quot;&gt;VerificationScript&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Lint&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Runs dotnet format to check code style&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;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Lint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IVerificationScript&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:#4EC9B0&quot;&gt; VerificationResult&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Execute&lt;/span&gt;&lt;span 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:#6A9955&quot;&gt;        // Run dotnet format --verify-no-changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // Return pass/fail based on exit code&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;Now you can use &lt;code&gt;ralph_verify(Lint) passes&lt;/code&gt; in your acceptance criteria. Security scans, API tests, whatever you need.&lt;/p&gt;
&lt;h2 id=&quot;behind-the-scenes&quot;&gt;Behind the Scenes&lt;/h2&gt;
&lt;p&gt;Every AI model has a context window-the amount of text it can &quot;see&quot; at once. Ralph works within these limits by keeping each story self-contained.&lt;/p&gt;
&lt;p&gt;A story must fit entirely within a single context window:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The system prompt (Ralph's instructions)&lt;/li&gt;
&lt;li&gt;The current story's details and acceptance criteria&lt;/li&gt;
&lt;li&gt;Relevant source files Ralph needs to read&lt;/li&gt;
&lt;li&gt;Space for Ralph to &quot;think&quot; and generate code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Practical guidance:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Story Size&lt;/th&gt;
&lt;th&gt;Files Touched&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;1-3 files&lt;/td&gt;
&lt;td&gt;✅ Ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;4-8 files&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;9-15 files&lt;/td&gt;
&lt;td&gt;⚠️ Consider splitting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Too Large&lt;/td&gt;
&lt;td&gt;15+ files&lt;/td&gt;
&lt;td&gt;❌ Split required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ralph doesn't have persistent memory across stories. Each time it picks one up, it starts fresh. This isn't a bug-it's a feature that forces clean design. Each story is a complete, verifiable unit of work.&lt;/p&gt;
&lt;p&gt;&quot;Add user authentication&quot; becomes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create User entity and migration&lt;/li&gt;
&lt;li&gt;Implement password hashing service&lt;/li&gt;
&lt;li&gt;Create login endpoint&lt;/li&gt;
&lt;li&gt;Add JWT token generation&lt;/li&gt;
&lt;li&gt;Implement authentication middleware&lt;/li&gt;
&lt;li&gt;Create registration endpoint&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each story is small enough to complete in one iteration, yet together they deliver the full feature.&lt;/p&gt;
&lt;h3 id=&quot;state-management&quot;&gt;State Management&lt;/h3&gt;
&lt;p&gt;Ralph maintains state at two levels:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PRD state&lt;/strong&gt; (persistent) - which stories exist, which have passed, notes from completed work. This file persists across sessions and gets committed to your repo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session state&lt;/strong&gt; (ephemeral) - current working story, in-progress changes, retry attempts. Lives only for one execution.&lt;/p&gt;
&lt;p&gt;If Ralph crashes mid-story, that story remains &lt;code&gt;passes: false&lt;/code&gt; and will be retried on next run. Completed stories are never redone.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;You'll need .NET 10 SDK and &lt;a href=&quot;https://docs.github.com/en/copilot/github-copilot-in-the-cli&quot;&gt;GitHub Copilot CLI&lt;/a&gt; installed and authenticated.&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;# Build and pack&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; build&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; pack&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Ralph/Ralph.csproj&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;# Install globally&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; tool&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --add-source&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Ralph/nupkg&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Ralph&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in your project:&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;# Option A: Plan and implement in one go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ralph&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;# Option B: Plan first, then implement separately&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ralph&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; plan&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    # Create the PRD interactively&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ralph&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;     # Implement all stories from the PRD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. Ralph asks &quot;What would you like to accomplish?&quot;, creates a PRD from your description, and implements all stories. Go grab coffee.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;Traditional AI assistants are designed for conversation. Ralph is designed for execution.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Traditional AI&lt;/th&gt;
&lt;th&gt;Ralph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;Degrades over time&lt;/td&gt;
&lt;td&gt;Fresh per story&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;You check manually&lt;/td&gt;
&lt;td&gt;Scripts verify automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human involvement&lt;/td&gt;
&lt;td&gt;Constant supervision&lt;/td&gt;
&lt;td&gt;Set and forget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The AI model is the same. The difference is the workflow architecture around it.&lt;/p&gt;
&lt;h2 id=&quot;the-catch&quot;&gt;The Catch&lt;/h2&gt;
&lt;p&gt;Stories need to be small enough to fit in a context window. If a story requires understanding 50 files at once, it'll struggle. Keep them focused: 1-5 files per story, one concern each.&lt;/p&gt;
&lt;p&gt;Also, this isn't magic. Ralph still needs good acceptance criteria. Vague requirements produce vague results. But that's true of any engineering work.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;AI coding assistants are great at short bursts but fall apart on longer tasks. Ralph fixes that by giving AI what it needs: structure. A PRD breaks work into discrete stories. Verification scripts confirm each one works. The result? An agent that can run for hours without wandering off course.&lt;/p&gt;
&lt;p&gt;The shift from &quot;AI assistant&quot; to &quot;AI agent&quot; isn't just semantic. It's the difference between babysitting and delegating.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Watch the demo&lt;/strong&gt; - See Ralph implement its own features in the &lt;a href=&quot;https://www.youtube.com/watch?v=dfZb2LPdkMk&quot;&gt;YouTube video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clone the repo&lt;/strong&gt; - &lt;a href=&quot;https://github.com/svnscha/ralph&quot;&gt;github.com/svnscha/ralph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Try it on something small&lt;/strong&gt; - Pick a well-defined feature, let Ralph work on it overnight, come back to commits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That moment when you return to working, tested code you didn't write line by line? That's when it clicks.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Ollama Now Speaks Messages API</title>
        <published>2026-01-22T00:00:00+00:00</published>
        <updated>2026-01-22T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/claude-code-ollama-support/"/>
        <id>https://svnscha.de/posts/claude-code-ollama-support/</id>
        <summary type="html">Ollama v0.14 supports the Anthropic Messages API, allowing Claude Code to work with local models.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/claude-code-ollama-support/">&lt;h2 id=&quot;the-news-ollama-learns-a-new-language&quot;&gt;The News: Ollama Learns a New Language&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Claude Code can now talk to your locally-hosted models&lt;/strong&gt; without any extra adapters, proxies, or dark magic.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Ollama v0.14.0 and later are now compatible with the Anthropic Messages API, making it possible to use tools like Claude Code with open-source models.&quot;
— &lt;a href=&quot;https://ollama.com/blog/claude&quot;&gt;Ollama Blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cue the excitement: &lt;em&gt;Finally! Unlimited coding on steroids! No more watching your token budget evaporate! 24/7 AI-assisted development without the API bill anxiety!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;...right?&lt;/p&gt;
&lt;h3 id=&quot;why-test-this-you-ask&quot;&gt;Why Test This, You Ask?&lt;/h3&gt;
&lt;p&gt;Naturally, I had to try this immediately. What better excuse to fire up the DGX Spark and see what these local models can really do?&lt;/p&gt;
&lt;p&gt;If you're running a DGX Spark (or any beefy GPU setup), this means you can now fire up Claude Code and have it talk to whatever model you've got running locally. Zero cloud dependency. Zero API costs eating into your coffee budget. Just pure, local AI goodness.&lt;/p&gt;
&lt;p&gt;But does it actually &lt;em&gt;work&lt;/em&gt;? Let's find out.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;setting-it-up&quot;&gt;Setting It Up&lt;/h2&gt;
&lt;h3 id=&quot;which-model&quot;&gt;Which Model?&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.ollama.com/integrations/claude-code&quot;&gt;official docs&lt;/a&gt; recommend a few options:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloud models&lt;/strong&gt; (if you're into that sort of thing):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;glm-4.7:cloud&lt;/code&gt;, &lt;code&gt;minimax-m2.1:cloud&lt;/code&gt;, &lt;code&gt;qwen3-coder:480b&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Local models&lt;/strong&gt; (the fun part):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qwen3-coder&lt;/code&gt; - Excellent for coding tasks&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpt-oss:20b&lt;/code&gt; - Strong general-purpose model&lt;/li&gt;
&lt;li&gt;&lt;code&gt;glm-4.7-flash&lt;/code&gt; - Deep reasoning, needs Ollama 0.14.3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I tested several of these locally - &lt;code&gt;qwen3-coder&lt;/code&gt; is genuinely impressive for coding tasks, and &lt;code&gt;glm-4.7-flash&lt;/code&gt; (just released with Ollama 0.14.3) surprised me with its deep reasoning approach. Cloud models? Kind of defeats the purpose of running local, doesn't it?&lt;/p&gt;
&lt;h3 id=&quot;bump-ollamas-context-window&quot;&gt;Bump Ollama's Context Window&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.ollama.com/integrations/claude-code&quot;&gt;docs recommend at least 32k context&lt;/a&gt;. Use &lt;code&gt;systemctl edit&lt;/code&gt; for a persistent override that survives updates:&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; edit&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ollama.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add this in the editor that opens:&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;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;OLLAMA_CONTEXT_LENGTH=32000&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;OLLAMA_FLASH_ATTENTION=1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;OLLAMA_KV_CACHE_TYPE=f16&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bonus performance tweaks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OLLAMA_FLASH_ATTENTION=1&lt;/code&gt; - Normal attention loads the entire context into memory at once. Flash Attention breaks it into chunks, processes them sequentially, and combines the results. Same output, way less memory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OLLAMA_KV_CACHE_TYPE=f16&lt;/code&gt; - Keeps the K/V cache at full precision (default). Use &lt;code&gt;q8_0&lt;/code&gt; to halve memory if you're tight on VRAM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How much context can you actually fit?&lt;/strong&gt; The &lt;a href=&quot;https://apxml.com/tools/vram-calculator&quot;&gt;VRAM Calculator&lt;/a&gt; is your friend. Play around with your model size, quantization, and available VRAM - it'll tell you the exact settings to max out each model.&lt;/p&gt;
&lt;p&gt;Then restart:&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; ollama.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;powershell-helper-for-your-work-machine&quot;&gt;PowerShell Helper for Your Work Machine&lt;/h3&gt;
&lt;p&gt;This little function lives in my &lt;code&gt;$PROFILE&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;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; Enter-ClaudeSpark&lt;/span&gt;&lt;span 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:ANTHROPIC_AUTH_TOKEN&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:#9CDCFE&quot;&gt;   $env:ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://spark-1:8080&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;   $env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;1&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;Now I just type &lt;code&gt;Enter-ClaudeSpark&lt;/code&gt; and Claude Code magically routes everything to my DGX Spark. The &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; is set to &lt;code&gt;ollama&lt;/code&gt; because Ollama doesn't actually need a real token - it just checks if the header exists. Clever.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;my-test-meeting-destruction-therapy&quot;&gt;My Test: Meeting Destruction Therapy&lt;/h2&gt;
&lt;p&gt;Public benchmarks do not always reflect how a model feels in everyday use. I care more about how well it follows my language and understands the task, so I made a small practical test of my own.&lt;/p&gt;
&lt;p&gt;I needed something creative, visual, and complex enough to separate the wheat from the chaff. What better way than asking these models to build a &lt;strong&gt;Breakout-style game where you &quot;reschedule&quot; your weekly meetings by smashing them with a paddle&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;The 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;md&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;# Breakout-Style Calendar Game 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:#569CD6;font-weight:bold&quot;&gt;## Objective&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;Build a &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**simple, modern Breakout-inspired browser game**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; where a bouncing ball clears &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**calendar-style blocks representing weekly meetings**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, and a horizontal paddle is used to “reschedule” (break) them.&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;## Core Concept&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; The playfield should resemble a &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**weekly calendar view**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; rather than a classic arcade board.&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; Each block represents a &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**meeting/event**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (rounded rectangles, subtle borders).&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; The paddle metaphorically “reschedules” meetings by bouncing the ball upward.&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; The ball “clears” meetings as they are hit.&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;## Visual &amp;amp; UX Direction (Very Important)&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:#569CD6;font-weight:bold&quot;&gt; **Look &amp;amp; feel inspired by modern calendar apps**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; such as &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**Google Calendar**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; or &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**Outlook Calendar**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&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; Soft neutral background (light or dark mode acceptable)&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; Rounded corners, subtle shadows, clean typography&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; Muted but intentional color palette (event colors feel like calendar categories)&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; Gentle grid or column structure reminiscent of a weekly view&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; Avoid retro/arcade styling (no pixel fonts, neon colors, or harsh outlines).&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; Animations should feel &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**smooth and calm**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, not frantic:&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; Subtle easing on ball movement&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; Soft fade/scale effects when meetings are cleared&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; Light hover or focus states where appropriate&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 Requirements&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; Use &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**only vanilla HTML, CSS, and JavaScript**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (no frameworks or libraries).&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; Single-page implementation.&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:#569CD6;font-weight:bold&quot;&gt; **Responsive and centered layout**&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; that works on desktop and mobile.&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; Canvas or DOM-based rendering is acceptable, but visuals should remain crisp.&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 should be clean, readable, and well-commented.&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;## Gameplay Requirements&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; Paddle controlled via mouse/touch (keyboard optional).&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; Ball physics should feel smooth and predictable.&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; Winning state when all meetings are cleared.&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; Optional: subtle sound effects or score labeled as &lt;/span&gt;&lt;span style=&quot;color:#569CD6;font-weight:bold&quot;&gt;**“Meetings Cleared”**&lt;/span&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;## Polish Encouraged&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; Thoughtful spacing and alignment&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; Calendar-like typography (system UI fonts preferred)&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; Micro-interactions and transitions that reinforce the scheduling metaphor&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;## Implementation&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; Create index.html, style.css and game.js.&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;When finished, suggest that the user can host and test the game locally using:&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;python -m http.server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I threw this at several models through Claude Code and timed the results:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Rating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Claude Opus 4.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2m 30s&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qwen3-coder:30b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1m 48s&lt;/td&gt;
&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpt-oss:20b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4m 24s&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-4.7-flash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8m 45s&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nemotron-3-nano:30b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2m 46s&lt;/td&gt;
&lt;td&gt;⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ministral-3:14b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;🚩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rnj-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;🚩&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;claude-opus-45-reference&quot;&gt;Claude Opus 4.5 (Reference)&lt;/h3&gt;
&lt;p&gt;Not self-hosted - this is the cloud-based frontline model, included for comparison. Understood the creative brief, nailed the game mechanics, produced clean and maintainable code. No surprises there.&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2026-01-22 opus.mp4&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h3 id=&quot;qwen3-coder30b&quot;&gt;qwen3-coder:30b&lt;/h3&gt;
&lt;p&gt;Faster than expected, and it actually produced something playable. At 30B parameters, genuinely impressive.&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2026-01-22 qwen3.mp4&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h3 id=&quot;gpt-oss20b&quot;&gt;gpt-oss:20b&lt;/h3&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2026-01-22 gpt oss.mp4&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2026-01-22-dgx-spark-gpt-oss-20b-usage.png&quot; alt=&quot;gpt-oss:20b resource usage&quot;&gt;&lt;/p&gt;
&lt;p&gt;For this one I went all out - maxed context window at 95k with full precision KV cache:&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:#569CD6&quot;&gt;Environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;OLLAMA_CONTEXT_LENGTH=95232&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;OLLAMA_FLASH_ATTENTION=1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;Environment&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;OLLAMA_KV_CACHE_TYPE=f16&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Solid result! The 20B variant delivered a working game with proper paddle controls. Not as polished as qwen3-coder, but definitely playable. A good middle-ground option if you want something from the GPT family.&lt;/p&gt;
&lt;h3 id=&quot;glm-47-flash&quot;&gt;glm-4.7-flash&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&quot;As the strongest model in the 30B class, GLM-4.7-Flash offers a new option for lightweight deployment that balances performance and efficiency.&quot;&lt;/em&gt; - Bold claims require testing. This one needs Ollama 0.14.3 (just released!).&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2026-01-22 glm.mp4&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;Why the 8+ minute runtime? GLM spends serious time in its thinking stage. Watch the Ollama output live and you'll see deep, methodical reasoning - no &quot;what if&quot; self-doubt loops, just clear and consistent problem-solving. The result shows: best physics of all the OSS models I tested. The ball movement felt smooth and responsive. The catch? It completely ignored the meeting-themed UI brief and went with a generic breakout style instead. So close, yet so far.&lt;/p&gt;
&lt;h3 id=&quot;nemotron-3-nano30b&quot;&gt;nemotron-3-nano:30b&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2026-01-22-dgx-spark-nemotron-3-nano-30b-breakout.png&quot; alt=&quot;nemotron-3-nano:30b&quot;&gt;&lt;/p&gt;
&lt;p&gt;It rendered something! Got the calendar theme, colorful meeting blocks, even a &quot;Meetings Cleared&quot; counter. But... where's the ball? Where's the paddle? Apparently Nemotron thought breakout meant &quot;break out of implementing game mechanics.&quot; Nice UI though.&lt;/p&gt;
&lt;p&gt;This one makes me a bit sad, honestly. NVIDIA has been releasing incredible AI stuff lately - the voice models alone are mind-blowing - but Nemotron-3-Nano just flopped here. Maybe it shines elsewhere, but for Claude Code workflows? Not ready.&lt;/p&gt;
&lt;h3 id=&quot;ministral-314b&quot;&gt;ministral-3:14b&lt;/h3&gt;
&lt;p&gt;I really tried with this one. Multiple attempts, different prompts, fresh sessions. Every single time: Claude Code just... completed. Immediately. No files, no code, no output. It's like the model and Claude Code looked at each other and mutually agreed to do nothing. 🤷&lt;/p&gt;
&lt;h3 id=&quot;rnj-1&quot;&gt;rnj-1&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&quot;8B parameter open-weight, dense models trained from scratch by Essential AI, optimized for code and STEM with capabilities on par with SOTA open-weight models.&quot;&lt;/em&gt; - 130k downloads, fresh release, sounded promising. The &lt;a href=&quot;https://www.essential.ai/research/rnj-1&quot;&gt;VSCode + Cline demo on their blog&lt;/a&gt; looked great - but that was a Python game, not our HTML/JS breakout challenge.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2026-01-22-dgx-spark-rnj-1-usage.png&quot; alt=&quot;rnj-1 resource usage&quot;&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, no results here. Another one that just didn't produce anything usable with Claude Code. At least the resource usage was... well, not surprising for an 8B model.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;So, Ollama now speaks the Messages API, and Claude Code can talk to local models. I put it through a fun benchmark - building a calendar breakout game - and the results were clear: Opus still delivers the goods, but the OSS models are catching up fast.&lt;/p&gt;
&lt;p&gt;For Claude Code agentic workflows, &lt;code&gt;qwen3-coder&lt;/code&gt; is the clear winner in the OSS space - fast, capable, and actually follows instructions. &lt;code&gt;glm-4.7-flash&lt;/code&gt; has potential but needs work on following prompts. &lt;code&gt;gpt-oss:20b&lt;/code&gt; is a solid middle-ground option.&lt;/p&gt;
&lt;p&gt;This actually reflects my broader model philosophy: ChatGPT-style models for basic tasks - rephrasing, docs, research. Anthropic for serious coding work. And now in the open-source world, &lt;code&gt;qwen3-coder&lt;/code&gt; is genuinely fun to work with.&lt;/p&gt;
&lt;p&gt;Is this going to replace Claude Opus for serious work? Not today. But for experimentation, learning, and those times when you want to see what the open-source world can do? This setup is fantastic.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Want to try this yourself?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Ollama ≥ 0.14: &lt;code&gt;curl -L https://ollama.com/install.sh | sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pull your model of choice: &lt;code&gt;ollama pull qwen3-coder:30b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bump the context window to at least 32k&lt;/li&gt;
&lt;li&gt;Add the PowerShell helper to your &lt;code&gt;$PROFILE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Fire up Claude Code and start breaking some meetings... I mean, coding&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And who knows - at the rate these models are improving, maybe in a few months I'll have to eat my words about frontline models being irreplaceable.&lt;/p&gt;
&lt;p&gt;In the meantime, building a game that lets you destroy your weekly meetings with a bouncing ball might be the most satisfying thing I've prompted this month.&lt;/p&gt;
&lt;p&gt;Happy hacking! 🎮&lt;/p&gt;
</content>
    </entry>
    <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>🚀 New Release: mcp-windbg 0.12.2</title>
        <published>2025-12-15T00:00:00+00:00</published>
        <updated>2025-12-15T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-0-12-2-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-0-12-2-release/</id>
        <summary type="html">Adding Prompts API support and streamable-http transport to mcp-windbg.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-0-12-2-release/">&lt;p&gt;I've just released version 0.12.2 of &lt;strong&gt;mcp-windbg&lt;/strong&gt;, bringing some significant enhancements that expand how and where you can perform crash dump analysis.&lt;/p&gt;
&lt;h2 id=&quot;whats-new&quot;&gt;What's New&lt;/h2&gt;
&lt;p&gt;This release introduces two major features that address the flexibility and deployment needs many of you have been asking about.&lt;/p&gt;
&lt;p&gt;First up is support for the &lt;strong&gt;Prompts API&lt;/strong&gt;, which fundamentally changes how you can interact with crash dump analysis. Rather than being tied to a local setup, you can now trigger triage analysis from anywhere in your environment. The server can be deployed on any machine in your network, whether that's a dedicated analysis box, a build server, or even directly on a file server hosting your crash dumps. This means you can centralize your debugging infrastructure and access it from wherever you're working.&lt;/p&gt;
&lt;p&gt;Second, this release adds &lt;strong&gt;streamable-http&lt;/strong&gt; as an alternative transport backend. This gives you more options for how the server communicates, particularly useful in containerized or networked environments where different transport mechanisms might be preferable.&lt;/p&gt;
&lt;p&gt;The combination of these features means you can now set up a centralized crash analysis service that developers across your team can leverage without needing to install WinDbg or configure everything locally. Want to analyze dumps directly on the file server where they're stored? Now you can.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;If you want to try out the latest version, installation is straightforward:&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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The project is open source and available on GitHub at &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;https://github.com/svnscha/mcp-windbg&lt;/a&gt;. If you find it useful, a star on the repository is always appreciated.&lt;/p&gt;
&lt;h2 id=&quot;a-real-world-example&quot;&gt;A Real-World Example&lt;/h2&gt;
&lt;p&gt;No release post would be complete without putting the tool to work on an actual crash dump. Here's an example of the analysis in action:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2025-12-15-mcp-windbg-copilot-crash.jpg&quot; alt=&quot;Crash Analysis&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Even Copilot needs a debugger sometimes.&lt;/em&gt;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>The Passenger Seat Developer</title>
        <published>2025-10-27T00:00:00+00:00</published>
        <updated>2025-10-27T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/the-passenger-seat-developer/"/>
        <id>https://svnscha.de/posts/the-passenger-seat-developer/</id>
        <summary type="html">Here's what nobody tells you about AI-assisted rapid prototyping: Every prototype feels like success, but success is measured by shipping, not building.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/the-passenger-seat-developer/">&lt;h2 id=&quot;the-great-prototype-experiment&quot;&gt;The Great Prototype Experiment&lt;/h2&gt;
&lt;p&gt;Here's what nobody tells you about AI-assisted rapid prototyping: After six months and nearly 100 projects, I had to face an uncomfortable truth.&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;Every prototype feels like success. But is it really?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Picture this: Me, armed with Claude, spinning up project after project like I'm some kind of coding machine gun. A productivity suite here, a file manager there, throw in some web scrapers, automation tools, data processors - you name it, I've probably built a prototype of it in the past six months.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nearly 100 projects.&lt;/strong&gt; The dopamine hits were insane. Every idea that popped into my head, no matter how half-baked, could become a working prototype in hours instead of weeks. The velocity felt otherworldly.&lt;/p&gt;
&lt;p&gt;The result took me too long to acknowledge: &lt;strong&gt;out of nearly 100 prototypes, only one reached production.&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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;One. Literally. One.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That project? &lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;mcp-windbg&lt;/a&gt;. And guess what made it special? It's the only one where I spent those classic, grinding hours sitting there, actually understanding every line of code, debugging the edge cases, and wrestling with the gnarly implementation details.&lt;/p&gt;
&lt;h2 id=&quot;the-dopamine-factory&quot;&gt;The Dopamine Factory&lt;/h2&gt;
&lt;p&gt;Let me be real with you about what those early days felt like. I'd wake up with some random idea - &quot;What if I built a tool that automatically organizes screenshots by content?&quot; or &quot;I need a better way to manage my development environments&quot; - and by lunch, I'd have a working prototype.&lt;/p&gt;
&lt;p&gt;Six months ago, I was writing about &lt;a href=&quot;/posts/vscode-vibe-coding/&quot;&gt;vibe coding&lt;/a&gt; like I'd discovered fire. The euphoria was real - watching AI refactor entire codebases, generate complex implementations, solve architectural problems in seconds. I was living in that perfect state where &quot;the barrier between thinking and implementing just got so much thinner.&quot;&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;Me: Build me a screenshot organizer that uses OCR to categorize images&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claude: *Generates Python app with OCR integration, file management, GUI*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Me: This actually works perfectly!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rush was incredible. Idea to implementation in hours. No tedious boilerplate. No debugging mysterious dependency issues. No wrestling with documentation. Just pure, frictionless creation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I felt like a goddamn wizard.&lt;/strong&gt; Every prototype that worked felt like validation that I'd cracked some secret code of productivity. Friends would ask what I was working on, and I'd rattle off a dozen projects like I was running a software factory.&lt;/p&gt;
&lt;p&gt;That euphoric beginning? It was exactly six months ago. Time has a funny way of providing perspective.&lt;/p&gt;
&lt;h2 id=&quot;the-reality-check-why-nothing-shipped&quot;&gt;The Reality Check: Why Nothing Shipped&lt;/h2&gt;
&lt;p&gt;After several months, my project folder had grown to nearly 100 directories, but I still had not shipped anything.&lt;/p&gt;
&lt;p&gt;Why? Because &lt;strong&gt;prototypes aren't products.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every time I'd come back to one of these AI-generated prototypes to actually finish it - to handle edge cases, add proper error handling, write tests, or make it production-ready - I'd hit the same wall: &lt;strong&gt;I had no clue how the thing actually worked.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The code looked familiar. The structure made sense at first glance. But when it came time to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debug why it crashed with certain file types&lt;/li&gt;
&lt;li&gt;Add a feature that required understanding the data flow&lt;/li&gt;
&lt;li&gt;Optimize performance bottlenecks&lt;/li&gt;
&lt;li&gt;Handle edge cases the AI hadn't considered&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was lost. Completely, utterly lost.&lt;/p&gt;
&lt;h2 id=&quot;the-one-that-made-it-mcp-windbg&quot;&gt;The One That Made It: mcp-windbg&lt;/h2&gt;
&lt;p&gt;So why did mcp-windbg succeed when 99 others failed? It wasn't because I spent months on it - it was a small weekend project. But I approached it differently.&lt;/p&gt;
&lt;p&gt;I started by vibe-coding with Claude, just like the other prototypes. Got the basic structure working, had it generating and parsing WinDBG commands. But then - and this is the crucial difference - I actually went back and reviewed what the AI had built. Debugged the edge cases myself. Traced through the subprocess communication to understand how it really worked.&lt;/p&gt;
&lt;p&gt;When pytest failures popped up, I didn't just ask Claude to fix them. I sat there and figured out why they were failing. When the CDB interaction got weird with certain commands, I debugged it manually until I understood the communication protocol.&lt;/p&gt;
&lt;p&gt;The result? A weekend project that actually solved a real problem in my daily work. Something I could confidently maintain, extend, and explain to others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I owned the code because I'd taken the time to understand what Claude had built.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-brutal-truth-about-ai-generated-prototypes&quot;&gt;The Brutal Truth About AI-Generated Prototypes&lt;/h2&gt;
&lt;p&gt;Those 99 failed projects weren't failures because the code was bad. Most of them actually worked pretty well for their basic use cases. They failed because I'd delegated understanding to AI instead of using it as a tool to support my understanding.&lt;/p&gt;
&lt;p&gt;Here is what was happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Week 1&lt;/strong&gt;: &quot;Holy shit, look at this amazing prototype!&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Week 2&lt;/strong&gt;: &quot;I should probably clean this up and ship it&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Week 3&lt;/strong&gt;: &quot;Hmm, there are some edge cases I need to handle&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Week 4&lt;/strong&gt;: &quot;Why is this crashing? Let me ask Claude to fix it&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Week 5&lt;/strong&gt;: &quot;The fix broke something else. This is getting messy&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Week 6&lt;/strong&gt;: &quot;Maybe I'll just start a new project instead...&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sound familiar? This cycle repeated so consistently I could set my calendar by it.&lt;/p&gt;
&lt;h2 id=&quot;the-sitting-there-hours-why-they-matter&quot;&gt;The Sitting-There Hours: Why They Matter&lt;/h2&gt;
&lt;p&gt;You know those classic hours every developer has experienced? The ones where you're just sitting there, staring at code, trying to figure out why something isn't working the way you expect? Those moments when you're debugging line by line, tracing execution paths, and slowly building up a mental model of how everything fits together?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Those hours aren't wasted time. They're investment time.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you skip those hours - when you let AI generate the solution and just accept it - you never build that intimate knowledge of your own system. You become a tourist in your own codebase.&lt;/p&gt;
&lt;p&gt;With mcp-windbg, I didn't spend weeks wrestling with it - it was just a weekend project. But during that weekend, when I hit issues with subprocess communication or weird CDB behavior, I actually debugged them myself instead of immediately asking Claude for fixes. I took the time to understand why certain commands failed and how the debugging session management worked.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That weekend of actually understanding what I was building made the difference between prototype #99 and shipped product #1.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-false-productivity-trap&quot;&gt;The False Productivity Trap&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I felt productive while building those 99 prototypes.&lt;/strong&gt; Each working demo felt like progress, and my GitHub activity supported that impression. It did not mean I was finishing useful products.&lt;/p&gt;
&lt;p&gt;But productivity isn't about how much code you generate. It's about how much value you ship. And by that metric, I had a 1% success rate.&lt;/p&gt;
&lt;p&gt;The problem was not AI itself, but how I used it. I treated it as a replacement for understanding instead of a debugger or knowledge base that could help me learn faster.&lt;/p&gt;
&lt;h2 id=&quot;how-i-use-ai-now&quot;&gt;How I Use AI Now&lt;/h2&gt;
&lt;p&gt;I still use AI regularly, but now I treat it like any other development tool.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI as a knowledge base&lt;/strong&gt;: &quot;How does subprocess communication work in Python?&quot; &quot;What are the edge cases for file parsing?&quot; &quot;Show me different approaches to error handling.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI as a debugging partner&lt;/strong&gt;: &quot;Here's my stack trace, what could be causing this?&quot; &quot;This function isn't behaving as expected, help me trace through the logic.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI for the boring repetitive stuff&lt;/strong&gt;: Boilerplate code, test scaffolding, documentation generation, refactoring patterns I've done a million times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI as a code reviewer&lt;/strong&gt;: &quot;Does this implementation handle edge cases properly?&quot; &quot;Are there performance issues with this approach?&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key difference: &lt;strong&gt;I use AI to accelerate my understanding, not replace it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For anything I might ship, the rule is simple: &lt;strong&gt;If I can't explain how the core functionality works without looking at the code, I don't ship it.&lt;/strong&gt; AI can help me get there faster, but it can't get there for me.&lt;/p&gt;
&lt;p&gt;This isn't about being a purist or rejecting AI assistance - it's about maintaining professional competence and shipping software you can actually support.&lt;/p&gt;
&lt;h2 id=&quot;what-success-actually-looks-like&quot;&gt;What Success Actually Looks Like&lt;/h2&gt;
&lt;p&gt;After six months of this experiment, here's what I've learned about successful AI-assisted development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Delegate the repetitive stuff&lt;/strong&gt;: Boilerplate generation, test scaffolding, documentation, refactoring patterns you've done before.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use it as a knowledge multiplier&lt;/strong&gt;: Research APIs, explore different approaches, get explanations of complex concepts, debug tricky issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep the understanding&lt;/strong&gt;: Architecture decisions, core business logic, data flow design, system integration points.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Invest in the sitting-there hours&lt;/strong&gt;: For anything you plan to ship, spend the time to truly understand how it works.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI is similar to a good debugger or profiler: it can make you more effective, but it does not replace understanding the system you are building.&lt;/p&gt;
&lt;h2 id=&quot;the-uncomfortable-question&quot;&gt;The Uncomfortable Question&lt;/h2&gt;
&lt;p&gt;Here's the question that keeps me up at night: &lt;strong&gt;How many developers are building careers on AI-generated code they don't understand?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is also a matter of professional responsibility. If you own a piece of code, you should be able to explain, modify, and debug it, especially when a production issue occurs and the AI assistant is unavailable.&lt;/p&gt;
&lt;p&gt;I don't have a good answer to that question. I just know I don't want to be that kind of developer.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;After six months and nearly 100 AI-assisted prototypes, only mcp-windbg shipped. The difference was that I used AI to speed up my learning instead of asking it to replace that learning.&lt;/p&gt;
&lt;p&gt;Remember that uncomfortable truth from the beginning? Every prototype feels like success, but success is measured by shipping, not building. The dopamine hits from rapid prototyping are real, but they're not the same as the satisfaction of maintaining code you actually understand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lesson isn't to use less AI. The lesson is to use it better.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI is like having a brilliant research assistant, debugger, and code reviewer all rolled into one. Use it to explore ideas faster, understand concepts deeper, and eliminate the boring repetitive work that burns you out. But don't delegate the understanding itself.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;If this resonates with you, here's what I'd suggest doing right now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit your current projects:&lt;/strong&gt; Look at your recent work. Can you explain how the core functionality works without looking at the code? If not, spend some time diving into those systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set a new rule:&lt;/strong&gt; For anything you plan to ship, implement the &quot;explain it without looking&quot; test. If you can't walk someone through how it works from memory, you're not ready to ship.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Change how you prompt:&lt;/strong&gt; Instead of &quot;Build me X,&quot; try &quot;Help me understand how to build X&quot; or &quot;What are the key concepts I need to know for X?&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Embrace the sitting-there hours:&lt;/strong&gt; When you hit a bug or weird behavior, resist the urge to immediately ask AI for a fix. Spend 15-20 minutes trying to understand it yourself first.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Someone still needs to own the code. AI can help you move faster, but it cannot take that responsibility from you.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>🚀 New Release: mcp-windbg 0.10.0</title>
        <published>2025-10-11T00:00:00+00:00</published>
        <updated>2025-10-11T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/mcp-windbg-0-10-0-release/"/>
        <id>https://svnscha.de/posts/mcp-windbg-0-10-0-release/</id>
        <summary type="html">AI-powered crash analysis gets a major upgrade with PyPI availability, live debugging sessions, and community-driven improvements. One-command installation is finally here.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/mcp-windbg-0-10-0-release/">&lt;h2 id=&quot;mcp-windbg-0100-now-available-on-pypi&quot;&gt;mcp-windbg 0.10.0: Now Available on PyPI&lt;/h2&gt;
&lt;p&gt;Five months after the initial release of &lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;mcp-windbg&lt;/a&gt;, I'm releasing version 0.10.0 with significant improvements and simplified deployment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mcp-windbg is now officially available on &lt;a href=&quot;https://pypi.org/project/mcp-windbg/&quot;&gt;PyPI&lt;/a&gt;.&lt;/strong&gt; Installation no longer requires cloning repositories or setting up development environments:&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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; mcp-windbg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This represents a significant simplification from the original setup process described in &lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;my first article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, mcp-windbg is now listed on the &lt;a href=&quot;https://github.com/modelcontextprotocol/registry&quot;&gt;official Model Context Protocol registry&lt;/a&gt;, which should improve discoverability and enable integrations with various MCP-compatible tools.&lt;/p&gt;
&lt;h2 id=&quot;release-0100-key-features-and-improvements&quot;&gt;Release 0.10.0: Key Features and Improvements&lt;/h2&gt;
&lt;p&gt;Version 0.10.0 incorporates community feedback and focuses on improving usability across different experience levels with debugging tools.&lt;/p&gt;
&lt;h3 id=&quot;new-features&quot;&gt;New Features&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Live Debugging Sessions&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;open_windbg_remote&lt;/code&gt; and &lt;code&gt;close_windbg_remote&lt;/code&gt; functions&lt;/li&gt;
&lt;li&gt;Support for connecting to live processes beyond crash dump analysis&lt;/li&gt;
&lt;li&gt;Real-time debugging through conversational AI interface&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Extended Dump File Support&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full compatibility with &lt;code&gt;.mdmp&lt;/code&gt; and &lt;code&gt;.hdmp&lt;/code&gt; formats&lt;/li&gt;
&lt;li&gt;Microsoft Store WinDbg CDB compatibility&lt;/li&gt;
&lt;li&gt;Improved automatic discovery of crash dump files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Development Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrated to &lt;code&gt;uv&lt;/code&gt; package manager for improved build performance&lt;/li&gt;
&lt;li&gt;Significantly reduced development setup and build times&lt;/li&gt;
&lt;li&gt;Streamlined dependency management&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;community-contributions&quot;&gt;Community Contributions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;@sooknarine&lt;/strong&gt; contributed several key improvements that made this release possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/svnscha/mcp-windbg/pull/6&quot;&gt;#6: Find local dumps with other common extensions&lt;/a&gt;&lt;/strong&gt; - Enhanced automatic discovery of crash dump files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/svnscha/mcp-windbg/pull/10&quot;&gt;#10: Add support for remote debugging&lt;/a&gt;&lt;/strong&gt; - Implemented live debugging session support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These contributions demonstrate the value of community involvement in open source development.&lt;/p&gt;
&lt;h3 id=&quot;infrastructure-improvements&quot;&gt;Infrastructure Improvements&lt;/h3&gt;
&lt;p&gt;Several behind-the-scenes improvements enhance reliability and maintainability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration&lt;/strong&gt;: Automated testing across Python versions 3.10 through 3.14&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Management&lt;/strong&gt;: Automated security updates and dependency maintenance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release Process&lt;/strong&gt;: Streamlined deployment for faster delivery of updates&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;documentation-updates&quot;&gt;Documentation Updates&lt;/h3&gt;
&lt;p&gt;The documentation has been restructured for better usability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt;: Comprehensive debugging instructions specifically for AI assistants&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured Templates&lt;/strong&gt;: &lt;code&gt;.github/prompts/dump-triage.prompt.md&lt;/code&gt; for consistent crash analysis (inspired by my &lt;a href=&quot;/posts/howto-extend-copilot-with-prompt-files/&quot;&gt;prompt engineering article&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository Wiki&lt;/strong&gt;: All documentation now lives in an easily searchable wiki&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplified README&lt;/strong&gt;: Focused on getting you started in minutes, not hours&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;This Changelog&lt;/strong&gt;: So you know what's changing and why&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;installation-from-tedious-to-trivial&quot;&gt;Installation: From Tedious to Trivial&lt;/h2&gt;
&lt;p&gt;Remember the old installation process? Clone, setup virtual environment, install dependencies, configure paths... ugh.&lt;/p&gt;
&lt;p&gt;Here's the new process:&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;# Install mcp-windbg&lt;/span&gt;&lt;/span&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; mcp-windbg&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;# That's it. Seriously.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add this to your &lt;code&gt;.vscode/mcp.json&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;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;servers&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;mcp_windbg&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;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stdio&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;command&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;python&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;args&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;-m&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;mcp_windbg&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:#9CDCFE&quot;&gt;			&quot;env&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;_NT_SYMBOL_PATH&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SRV*C:&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Symbols*https://msdl.microsoft.com/download/symbols&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;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 installation process is now considerably simplified compared to the previous manual setup requirements.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;With 0.10.0 released, these are the next areas I plan to work on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More AI integrations&lt;/strong&gt; beyond VS Code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced live debugging capabilities&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance optimizations&lt;/strong&gt; for large dump files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Community-requested features&lt;/strong&gt; (keep them coming!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The development roadmap will continue to incorporate community feedback and feature requests.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;To begin using mcp-windbg 0.10.0:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Install&lt;/strong&gt;: &lt;code&gt;pip install mcp-windbg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure&lt;/strong&gt;: Add the MCP server to your VS Code configuration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use&lt;/strong&gt;: Load crash dumps and interact through natural language queries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute&lt;/strong&gt;: Report issues, suggest features, or contribute code improvements&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.org/project/mcp-windbg/&quot;&gt;PyPI Package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/registry&quot;&gt;MCP Registry Listing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;Article: AI Meets WinDbg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/howto-extend-copilot-with-prompt-files/&quot;&gt;Article: Prompt Engineering with Copilot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;community-response-and-impact&quot;&gt;Community Response and Impact&lt;/h2&gt;
&lt;p&gt;Since the initial release in May, the project has received significant community engagement. The &lt;a href=&quot;/posts/ai-meets-windbg/&quot;&gt;original article&lt;/a&gt; generated over 40,000 page views within the first two days, along with thousands of LinkedIn impressions and direct messages from developers worldwide.&lt;/p&gt;
&lt;p&gt;The feedback has been constructive and encouraging. Developers have shared how the tool helped reduce time spent on crash analysis, and several teams have integrated it into their debugging workflows. I've had opportunities to present this work both internally and externally, leading to valuable discussions about AI-assisted development tools.&lt;/p&gt;
&lt;p&gt;The open source community response has been particularly gratifying. Contributors like &lt;a href=&quot;https://github.com/sooknarine&quot;&gt;@sooknarine&lt;/a&gt; have stepped in to add functionality that benefits all users. The GitHub repository has gained considerable attention, and the project's inclusion in the official MCP registry should further improve its discoverability.&lt;/p&gt;
&lt;p&gt;Building something that genuinely helps other developers solve real problems has been rewarding beyond what I expected when I first started working on this as a way to improve my own debugging workflow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Thanks to everyone who has contributed, shared feedback, or simply tried the tool. The community response has made this project much better than it would have been otherwise.&lt;/em&gt;&lt;/p&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>AI Meets WinDBG: A Different Way to Analyze Crashes</title>
        <published>2025-05-04T00:00:00+00:00</published>
        <updated>2025-05-04T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-meets-windbg/"/>
        <id>https://svnscha.de/posts/ai-meets-windbg/</id>
        <summary type="html">Because manually squinting at hex dumps is so last century. Let me show you how AI-assisted debugging is leaving WinDBG's command line in the dust.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-meets-windbg/">&lt;h2 id=&quot;old-meets-new-bringing-crash-analysis-into-2025&quot;&gt;Old Meets New: Bringing Crash Analysis into 2025&lt;/h2&gt;
&lt;p&gt;Let's face it - while the rest of software development has evolved at warp speed, crash dump analysis feels like it's been preserved in digital amber for decades. We've got self-driving cars and pocket-sized supercomputers, yet here we are, still pecking away at command prompts like it's the dawn of the internet. Why is debugging the only area where we cling to tools that look like they belong in a computer history museum?&lt;/p&gt;
&lt;p&gt;Picture this: You, a professional software engineer in 2025, hunched over a terminal, manually typing arcane commands like &lt;code&gt;!analyze -v&lt;/code&gt; and &lt;code&gt;.ecxr&lt;/code&gt;, squinting at hexadecimal memory addresses, and mentally translating stack traces. All while your friends in other industries are delegating their work to AI assistants that can write entire documents, create art, or automate complex workflows.&lt;/p&gt;
&lt;p&gt;Something's wrong with this picture, right?&lt;/p&gt;
&lt;p&gt;I wanted a simpler workflow: ask questions in plain language and let an AI assistant run the relevant debugger commands. This is more than a new interface for WinDBG; it changes how you interact with the debugger.&lt;/p&gt;
&lt;h2 id=&quot;when-inspiration-strikes&quot;&gt;When Inspiration Strikes&lt;/h2&gt;
&lt;p&gt;During a debugging session at work, I had one of those lightning bolt moments. What if - and stick with me here - we could apply the same AI-assisted &quot;vibe coding&quot; approach to crash dump analysis?&lt;/p&gt;
&lt;p&gt;Picture this: instead of manually slogging through memory dumps and command outputs, you simply ask, &quot;Hey, why did this application crash?&quot; and get an intelligent, contextual answer that actually helps you solve the problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The idea was too compelling not to pursue. So I built it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;see-it-in-action-ai-powered-crash-analysis&quot;&gt;See It In Action: AI-Powered Crash Analysis&lt;/h2&gt;
&lt;p&gt;Before diving into the technical details, let me show you what this looks like in practice. I have prepared a demo application to showcase two different use cases:&lt;/p&gt;
&lt;h3 id=&quot;video-1-crash-analysis-and-automated-bugfix&quot;&gt;Video 1: Crash Analysis and Automated Bugfix&lt;/h3&gt;
&lt;p&gt;In this video, I show how Copilot can analyze a crash dump, identify the bug and auto-fix the issue.&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2025-05-03-CrashDump1.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;As you can see in the video, instead of manually running WinDBG commands and interpreting the cryptic output, I'm having a natural conversation with GitHub Copilot. The AI quickly identifies that the application crashed, explains which specific conditions led to the crash, and suggests a fix.&lt;/p&gt;
&lt;h3 id=&quot;video-2-automated-crash-dump-analysis-of-multiple-crash-dump-files&quot;&gt;Video 2: Automated Crash Dump Analysis of multiple crash dump files&lt;/h3&gt;
&lt;p&gt;This video demonstrates a different capability: analyzing multiple crash dump files at once. It shows how the tool can quickly identify which dumps belong to your application and which don't.&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2025-05-03-CrashDump2.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;Worth noting, it takes just a few seconds until you get your first useful answer. I've played around with this for many hours and let me tell you one thing: You can really go deep. If you ask the right questions, the AI runs WinDBG/CDB commands that I haven't seen in all these years of debugging, and that is simply amazing.&lt;/p&gt;
&lt;h2 id=&quot;how-can-this-help-the-industry&quot;&gt;How can this help the industry?&lt;/h2&gt;
&lt;p&gt;I believe this is one of the really good examples of how AI can boost productivity. Analyzing crash dumps is a very tedious task. It begins with quickly checking and identifying whether crashes are the same or different, and often requires very advanced knowledge when a crash is challenging - really challenging.&lt;/p&gt;
&lt;p&gt;Copilot can help here tremendously; it knows how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interpret assembly code (without you having to remember what EAX stands for)&lt;/li&gt;
&lt;li&gt;Check memory contents (so you don't have to count hex bytes on your fingers)&lt;/li&gt;
&lt;li&gt;Traverse structures with symbols (goodbye to manual pointer arithmetic!)&lt;/li&gt;
&lt;li&gt;And so much more&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This can help engineers, support teams, and QA staff work with crash dumps without memorizing every debugger command.&lt;/p&gt;
&lt;h2 id=&quot;how-did-i-build-this&quot;&gt;How did I build this?&lt;/h2&gt;
&lt;p&gt;If you've ever worked with WinDBG, you know the drill: cryptic commands, obscure syntax, and endless scrolling through memory addresses and stack traces that make your eyes glaze over. It's the kind of specialized knowledge that takes years to master and feels like speaking an alien language even when you do.&lt;/p&gt;
&lt;p&gt;The trick here is connecting WinDBG with AI. To do that, you first need to programmatically control a debugging session, right? There are plenty of options on how to do this. I prefer to keep things simple, so I have chosen &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/debugging-using-cdb-and-ntsd&quot;&gt;CDB&lt;/a&gt;, which is Microsoft's Console Debugger. It operates on standard input and output, and that's so much more fun to deal with than setting up COM APIs or similar approaches.&lt;/p&gt;
&lt;p&gt;The second part is &quot;connecting with AI.&quot; That's where Model Context Protocol Servers come into the game.&lt;/p&gt;
&lt;h2 id=&quot;understanding-model-context-protocol-servers&quot;&gt;Understanding Model Context Protocol Servers&lt;/h2&gt;
&lt;p&gt;MCP is an open standard developed by Anthropic, released in November 2024. This protocol allows AI models to interact with external tools and data sources - think of it as giving AI assistants &quot;hands&quot; to work with other software. It defines a way for AI assistants to discover, access, and use tools through a consistent interface. In essence, it's what allows GitHub Copilot to &quot;talk&quot; to external programs like WinDBG.&lt;/p&gt;
&lt;p&gt;An MCP server acts as the intermediary between the AI model and the tool. It:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Registers available tools with the client&lt;/li&gt;
&lt;li&gt;Handles requests from AI models to use these tools&lt;/li&gt;
&lt;li&gt;Executes the tool operations and returns results&lt;/li&gt;
&lt;li&gt;Maintains context across interactions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Any tool can be made available to an AI model through an MCP server. I built one for WinDBG's CDB debugger.&lt;/p&gt;
&lt;h3 id=&quot;why-mcp-instead-of-languagemodeltool-api&quot;&gt;Why MCP Instead of LanguageModelTool API?&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://code.visualstudio.com/api/extension-guides/tools&quot;&gt;LanguageModelTool API&lt;/a&gt; might eventually be a better fit for this specific use-case. Creating a Visual Studio Extension that &quot;just works&quot; out of the box would potentially simplify the integration process significantly.&lt;/p&gt;
&lt;p&gt;However, using MCP directly offers several notable advantages. It works with any AI model, not just limiting itself to Copilot. The server can be used outside VS Code, functioning with various other tools. New features can be easily added without necessitating changes to the core integration. Moreover, it remains platform-independent, avoiding lock-in to any single company's implementation.&lt;/p&gt;
&lt;h2 id=&quot;the-mcp-windbg-project&quot;&gt;The MCP-WinDBG Project&lt;/h2&gt;
&lt;p&gt;I've implemented a &lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot;&gt;Model Context Protocol&lt;/a&gt; server that wraps WinDBG/CDB and exposes its capabilities to AI models within VS Code. Better yet, I've made it open source so everyone can experience this new workflow.&lt;/p&gt;
&lt;p&gt;The project, &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;mcp-windbg&lt;/a&gt;, connects VS Code and GitHub Copilot to WinDBG's analysis commands.&lt;/p&gt;
&lt;p&gt;The actual &quot;hard part&quot; was implementing the CDB (Command-Line WinDBG) interaction layer. And by &quot;hard&quot;, I mean vibe-coding with two coffees on a Saturday morning, where I spent more time being annoyed by pyTest failures than actual coding difficulties. The core implementation came together surprisingly quickly!&lt;/p&gt;
&lt;p&gt;The rest is primarily wrapper code that implements the Model Context Protocol specifications. Now that I've established and defined the core WinDBG interaction logic, I'm considering refactoring the project to TypeScript. This would enable me to create both an MCP Server in TypeScript and a dedicated Visual Studio Extension, with both implementations leveraging the same underlying CDB interaction layer.&lt;/p&gt;
&lt;h2 id=&quot;what-does-this-mean-in-practice&quot;&gt;What Does This Mean In Practice?&lt;/h2&gt;
&lt;p&gt;Let me walk you through what this enables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Natural language crash analysis&lt;/strong&gt;: &quot;Why is this application crashing with an access violation at this address?&quot; (Instead of: &quot;What the $%#@ is this heap corruption!?&quot;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Contextual debugging&lt;/strong&gt;: &quot;Show me the stack trace for thread 5 and explain what each function is doing based on the symbols.&quot; (Instead of staring at call stacks like they're ancient hieroglyphics)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Root cause identification&lt;/strong&gt;: &quot;What's causing this null pointer dereference and where should I look in the code to fix it?&quot; (Instead of playing detective with memory addresses)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Instead of typing obscure commands like &lt;code&gt;!analyze -v&lt;/code&gt; followed by a series of manual investigations, you simply ask questions in plain language, and the AI interprets the crash data for you. It's like having a WinDBG expert whispering in your ear, except it doesn't get annoyed when you ask the same question five times.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;The MCP server connects GitHub Copilot to WinDBG:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It provides a set of tools that Copilot can use to interact with crash dumps&lt;/li&gt;
&lt;li&gt;It translates natural language questions into appropriate WinDBG commands&lt;/li&gt;
&lt;li&gt;It parses and interprets the often cryptic WinDBG output into more useful information&lt;/li&gt;
&lt;li&gt;It maintains context throughout a debugging session, enabling follow-up questions to work naturally&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The technical implementation uses Python to spawn and communicate with CDB (the command-line version of WinDBG), parses the output, and exposes the functionality through the Model Context Protocol to VS Code.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-mcp-windbg&quot;&gt;Getting Started With mcp-windbg&lt;/h2&gt;
&lt;p&gt;Ready to try it yourself? Here's how to get started:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, make sure you have the Windows SDK installed with Debugging Tools for Windows&lt;/li&gt;
&lt;li&gt;Clone the repository: &lt;code&gt;git clone https://github.com/svnscha/mcp-windbg.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set up a Python virtual environment and install the package&lt;/li&gt;
&lt;li&gt;Configure VS Code to use the MCP server&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For complete details, check out the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;repository README&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once configured, create a &lt;code&gt;.vscode/mcp.json&lt;/code&gt; file in your project that points to 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;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;servers&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;mcp_server_windbg&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;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stdio&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;command&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;python&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;args&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;-m&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;mcp_server_windbg&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:#9CDCFE&quot;&gt;            &quot;env&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;_NT_SYMBOL_PATH&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SRV*C:&lt;/span&gt;&lt;span style=&quot;color:#D7BA7D&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Symbols*https://msdl.microsoft.com/download/symbols&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;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 might need to update the command, depending on where and how you have installed the mcp_server_windbg to.&lt;/p&gt;
&lt;h2 id=&quot;the-human-touch-still-matters&quot;&gt;The Human Touch Still Matters&lt;/h2&gt;
&lt;p&gt;Just like with &lt;a href=&quot;/posts/vscode-vibe-coding/&quot;&gt;code refactoring&lt;/a&gt;, the AI assistance isn't perfect. The human element - your experience, intuition, and domain knowledge - remains crucial. Sometimes you'll need to guide the analysis, ask follow-up questions, or provide additional context.&lt;/p&gt;
&lt;p&gt;The useful part is the combination: AI can process a large amount of debugger output quickly, while you provide the context needed to judge the result. It still needs supervision and can follow the wrong lead, so treat its findings as a starting point rather than a final answer.&lt;/p&gt;
&lt;h2 id=&quot;join-the-experience&quot;&gt;Join The Experience&lt;/h2&gt;
&lt;p&gt;I'd love for you to try this out, contribute to the project, and share your experiences. If you're interested:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Star the &lt;a href=&quot;https://github.com/svnscha/mcp-windbg&quot;&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Try it on your own crash dumps&lt;/li&gt;
&lt;li&gt;Report issues, suggest improvements, or contribute code&lt;/li&gt;
&lt;li&gt;Share your success stories (or even failures - we learn from those too!)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;the-magic-is-in-the-flow&quot;&gt;The Magic Is In The Flow&lt;/h2&gt;
&lt;p&gt;Just like with my code refactoring experience, the real magic isn't about any single capability - it's about the flow. When debugging stops being a tedious chore and becomes a fluid conversation, something fundamentally changes in how you approach problem-solving.&lt;/p&gt;
&lt;p&gt;Gone are the days of dreading crash analysis. Instead, each debugging session becomes an opportunity for collaboration with an AI partner that helps you understand what's happening at a deeper level.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Crash dump analysis has traditionally been one of the most technically demanding and least enjoyable parts of software development. It's like archaeology with a keyboard - painstakingly excavating through layers of memory and CPU state to unearth what went wrong. With AI assistance through tools like mcp-windbg, it becomes another area where we can experience that perfect &quot;vibe state&quot; of frictionless problem-solving.&lt;/p&gt;
&lt;p&gt;If you're still manually typing WinDBG commands and squinting at memory dumps in 2025, you're not just missing out on productivity - you're missing out on a fundamentally more enjoyable way to work.&lt;/p&gt;
&lt;p&gt;Try it. Debug it. Vibe it.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Vibe Coding is So Damn Real</title>
        <published>2025-04-27T00:00:00+00:00</published>
        <updated>2025-04-27T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/vscode-vibe-coding/"/>
        <id>https://svnscha.de/posts/vscode-vibe-coding/</id>
        <summary type="html">Because watching GitHub Copilot refactor files with a single prompt is the most satisfying thing you'll see today.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/vscode-vibe-coding/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Ever had that moment when your coding flow is so perfect it feels like you and your tools are in some kind of cosmic alignment? That's vibe coding. It's when the friction between thought and implementation practically disappears. And let me tell you, with GitHub Copilot and VS Code, this isn't just a fleeting sensation-it's a consistent reality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I'm tired of writing about theoretical capabilities. Let me show you something concrete.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;a-childhood-dream-come-true&quot;&gt;A Childhood Dream Come True&lt;/h2&gt;
&lt;p&gt;Let me share something personal: since I first started coding, I've had this recurring dream. A dream where I could simply talk to my computer, tell it what I want in plain language, and watch as it translates my intentions into working code. No syntax gymnastics. No hunting for semicolons. Just pure, frictionless creation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Hey computer, refactor this module to use dependency injection instead of globals.&quot;&lt;/li&gt;
&lt;li&gt;&quot;Computer, optimize this database query, it's running too slow.&quot;&lt;/li&gt;
&lt;li&gt;&quot;Add translation support to all user-facing strings in this module.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For decades, this remained firmly in the realm of science fiction-something for Star Trek episodes where they casually ask the computer to &quot;reconfigure the deflector dish&quot; and it just... happens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DAMN, we're just there now.&lt;/strong&gt; Not in some distant future, not as a tech demo, but as a practical, everyday reality in my development workflow.&lt;/p&gt;
&lt;h2 id=&quot;real-world-context-nofidoc&quot;&gt;Real-World Context: Nofidoc&lt;/h2&gt;
&lt;p&gt;A little context: In my free time, I've been pushing the boundaries of what's possible with AI-assisted coding-well beyond just casual experiments. While I also use these workflows at work, Nofidoc has become my personal playground for exploring how far I can take this technology. It's a space where I can try things that aren't always ready for the spotlight, but the results have been too compelling not to share (even if I'm keeping things a bit low-key for now).&lt;/p&gt;
&lt;p&gt;The codebase uses Qt for the UI components, has a wizard-based document import flow, and handles various document formats with a focus on PDFs. It's exactly the kind of mid-sized C++ project where refactoring used to be tedious and error-prone.&lt;/p&gt;
&lt;h2 id=&quot;the-perfect-pairing&quot;&gt;The Perfect Pairing&lt;/h2&gt;
&lt;p&gt;VS Code with GitHub Copilot doesn't just assist your coding-it transforms it. No more wasting precious brain cycles on trivial tasks. No more tedious refactoring operations that make you question your career choices at 2 AM.&lt;/p&gt;
&lt;p&gt;I recently needed to refactor the document import workflow to remove direct database dependencies in the wizard component. Why having that in the first place? Well, getting something proof-of-concept-ish is the answer. Anyways, this is a classic case of improving architecture by decoupling components, but it typically requires careful changes across multiple files and understanding the entire call hierarchy.&lt;/p&gt;
&lt;h2 id=&quot;see-the-entire-workflow-in-action&quot;&gt;See The Entire Workflow In Action&lt;/h2&gt;
&lt;p&gt;I've recorded a full ~6-minute video showing the entire refactoring process from start to finish. You'll see me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Prompt Copilot with my refactoring requirements&lt;/li&gt;
&lt;li&gt;Watch the initial code changes being proposed&lt;/li&gt;
&lt;li&gt;Hit compilation errors (because real-world code is messy!)&lt;/li&gt;
&lt;li&gt;Provide additional guidance to Copilot&lt;/li&gt;
&lt;li&gt;Get a complete, working solution&lt;/li&gt;
&lt;/ol&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/2025-04-27-vibe.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;What's remarkable here is not just that Copilot could make the changes, but how the entire interaction feels like working with a senior developer who deeply understands your codebase.&lt;/p&gt;
&lt;h2 id=&quot;the-real-example-documentimportwizard-refactoring&quot;&gt;The Real Example: DocumentImportWizard Refactoring&lt;/h2&gt;
&lt;p&gt;Here's a snapshot of what happened in the video. I needed to update the DatabaseWidget class to use the new API of our DocumentImportWizard that we had refactored to remove direct database dependencies.&lt;/p&gt;
&lt;p&gt;Copilot analyzed the code and came up with this solution:&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:#6A9955&quot;&gt;// Before refactoring, direct database dependency:&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:#4EC9B0&quot;&gt; DatabaseWidget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onDocumentImportWizard&lt;/span&gt;&lt;span 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;    auto&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; importWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; DocumentImportWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_database&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span 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;    importWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;exec&lt;/span&gt;&lt;span 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;// After refactoring, using callback approach:&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:#4EC9B0&quot;&gt; DatabaseWidget&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;onDocumentImportWizard&lt;/span&gt;&lt;span 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;    auto&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; importWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; DocumentImportWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span 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;    importWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;setGetDocumentTypesCallback&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span 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;_database&lt;/span&gt;&lt;span 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:#DCDCAA&quot;&gt; QList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;DocumentType&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;        auto&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; docTypeService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_database&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getDocumentTypeService&lt;/span&gt;&lt;span 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;docTypeService&lt;/span&gt;&lt;span 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:#DCDCAA&quot;&gt; QList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;DocumentType&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;        return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; docTypeService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;getDocumentTypes&lt;/span&gt;&lt;span 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;    importWizard&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;exec&lt;/span&gt;&lt;span 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;Copilot explained its reasoning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&quot;I've updated the onDocumentImportWizard() method in DatabaseWidget to use the new refactored API of our DocumentImportWizard class. The key changes were:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;1. Removed passing the _database parameter directly to the DocumentImportWizard constructor&lt;/em&gt;
&lt;em&gt;2. Added a call to setGetDocumentTypesCallback() with a lambda that:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checks if _database is valid&lt;/li&gt;
&lt;li&gt;Gets the document type service from the database&lt;/li&gt;
&lt;li&gt;Returns the document types list from the service&lt;/li&gt;
&lt;li&gt;Returns an empty list if any step fails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;This completes the refactoring work by updating all the calling code to use the new API. The change maintains the same functionality but with a cleaner design that removes direct dependencies on the database pointer.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The implementation follows modern C++ practices by using a lambda to encapsulate the specific functionality needed (getting document types) while keeping the rest of the database access logic out of the wizard class. This makes the code more maintainable and easier to test.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is exactly the kind of architectural improvement that typically takes deep understanding of both the codebase and software design principles. Yet Copilot produced it in seconds.&lt;/p&gt;
&lt;h2 id=&quot;the-key-lesson-human-ai-collaboration&quot;&gt;The Key Lesson: Human-AI Collaboration&lt;/h2&gt;
&lt;p&gt;My main takeaway is simple: &lt;strong&gt;AI tools work best when you bring your own domain knowledge.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just before that, I was debugging a frustrating issue with field registration in that document import wizard. The fields weren't passing values between pages correctly. I asked Copilot to help fix the bug and it initially proposed an overly complex workaround that would have been a maintenance nightmare.&lt;/p&gt;
&lt;p&gt;So I gave it a hint:&lt;/p&gt;
&lt;p&gt;&quot;I think there's an issue with the registerField calls for 'importSuccessCount' and 'importErrorCount'. The implementation doesn't specify the property name correctly.&quot;&lt;/p&gt;
&lt;p&gt;And just like that, it had an &quot;aha!&quot; moment. It immediately identified the real issue:&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:#6A9955&quot;&gt;// Before (buggy implementation):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;registerField&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;importSuccessCount&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&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;// After (Copilot's correct fix):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;registerField&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;importSuccessCount&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;this&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;importSuccessCount&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 simple fix resolved the entire issue. The fix works because it properly uses the Qt property system (third parameter) as the remaining code and pages expected, whereas Copilot's initial workaround was mega ugly - setting properties directly with setProperty() calls. With just a small hint about the actual problem, we were back on track. This perfectly illustrates why domain expertise remains critical - AI tools can suggest solutions, but knowing which ones make sense in your framework requires human judgment.&lt;/p&gt;
&lt;p&gt;Sometimes AI tools need a gentle nudge in the right direction, and then they're off to the races again.&lt;/p&gt;
&lt;h2 id=&quot;not-always-perfect-magic&quot;&gt;Not Always Perfect Magic&lt;/h2&gt;
&lt;p&gt;Let me be real with you - it's not all sunshine and rainbows. AI assistance sometimes misses the mark, and when it does, you need to steer it in the right direction. But I've found that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The initial attempt is usually close&lt;/li&gt;
&lt;li&gt;AI is exceptionally good at understanding feedback&lt;/li&gt;
&lt;li&gt;With the right hints, it can quickly recover and find the optimal solution&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The collaboration workflow is what makes all the difference. Instead of wrestling with a tool, it feels like having a junior developer who's eager to learn and quick to adapt.&lt;/p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;This isn't just about saving time (though I saved HOURS). It's about changing how we think about coding. When you can express your intent in natural language and have it accurately translated into code changes, you're no longer programming computers-you're conversing with them.&lt;/p&gt;
&lt;p&gt;The barrier between thinking and implementing just got so much thinner.&lt;/p&gt;
&lt;h2 id=&quot;getting-this-setup-yourself&quot;&gt;Getting This Setup Yourself&lt;/h2&gt;
&lt;p&gt;Want this superpower? Here's what you need:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;VS Code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A clear understanding of your codebase&lt;/strong&gt; - To craft effective prompts&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's it! No complex configuration, no elaborate plugin chains.&lt;/p&gt;
&lt;p&gt;Oh, which model? Right now I am using Claude 3.7 - it's so good.&lt;/p&gt;
&lt;h2 id=&quot;where-this-is-going&quot;&gt;Where This Is Going&lt;/h2&gt;
&lt;p&gt;You know that saying about technology and magic? This is it. We're witnessing the early days of a transformation in how software gets built. The tools are evolving to understand not just the syntax of our code, but the intent behind it.&lt;/p&gt;
&lt;p&gt;Could I have written a complex bash script to do some of this refactoring? Maybe.
Could I have used search-and-replace with regex? Probably.
Would either approach have understood the semantic meaning of my code well enough to know which strings need translation? Absolutely not.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Vibe coding isn't just some feel-good developer term. It's that perfect state where you and your tools are in sync, where expressing your intent feels natural and immediate. With GitHub Copilot and VS Code, I'm experiencing this daily while building Nofidoc.&lt;/p&gt;
&lt;p&gt;If you're still doing manual refactoring in 2025, you're not just missing out on productivity-you're missing out on the pure joy of frictionless development.&lt;/p&gt;
&lt;p&gt;Try it. Feel it. Vibe code.&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>
</feed>
